用C++从数字数组创建png/bitmap

10

所以我找到了这个链接,关于我的问题,但是它是针对C#的:

从字节数组创建PNG图像

我有一个包含整数的变量数组。我将其称为"pix[] "。 现在它可以是任何大小,从3到256,以后可能更大。

现在我想做的是将它转换成像素图像。 我还是一个c++新手,所以请见谅。 我尝试下载一些使与libpng一起工作更容易的库,但它们似乎没有起作用(Ubuntu,Code::Blocks)。 因此我有以下问题:

1)如何创建新的位图(使用哪些库,哪个命令)?

2)如何使用“pix[]”的信息填充它?

3)如何保存它?

如果这是问题的转载,我也很高兴获得链接;)

这是我迄今为止的工作成果,感谢您的帮助。

int main(){
 FILE *imageFile;
 int x,y,pixel,height=2,width=3;

 imageFile=fopen("image.pgm","wb");
 if(imageFile==NULL){
  perror("ERROR: Cannot open output file");
  exit(EXIT_FAILURE);
 }

 fprintf(imageFile,"P3\n");           // P3 filetype
 fprintf(imageFile,"%d %d\n",width,height);   // dimensions
 fprintf(imageFile,"255\n");          // Max pixel
  int pix[100] {200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};


       fwrite(pix,1,18,imageFile);


fclose(imageFile);
}

我还没有完全理解它的作用。 我可以打开输出图像,但它不是数组的正确表示。

如果我改变一些东西,例如使一个二维数组,那么图像查看器告诉我“期望整数”,并且没有显示图像。

到目前为止都还好。由于我有了数组,所以创建了一个名为aufrunden的函数,以四舍五入到下一个整数,因为我想创建一个正方形图像。

int aufrunden (double h)
{
int i =h;
if (h-i == 0)
  {
  return i;
  }
else
  {
  i = h+1;
  return i;
  }
}

这个函数被用于创建图像。如果图像比数组提供的信息大,就像这样(a 是数组的长度)。

double h;
h= sqrt(a/3.0);
int i = aufrunden(h);
FILE *imageFile;                           
int height=i,width=i;

现在可能会出现数组长度为a=24的情况。aufrunden将图像变为3x3,因此它有27个值...这意味着缺少1个像素的值。 更糟糕的是,它只有a=23长,也创建了一个3x3的图像。

fwrite(pix,1,18,imageFile);将在这些像素中写入什么信息?最好剩余的值都为0。

*编辑,不用担心,我会在数组末尾添加0,直到填满整个正方形...对不起


如果您只需要结果PNG文件,可以使用Qt框架。它包含适当的QPixmap类,其中有一个有用的loadFromData方法。这是附加参考:http://doc.qt.io/qt-5/qpixmap.html#loadFromData。输出PNG文件可以使用save方法保存(http://doc.qt.io/qt-5/qpixmap.html#save)。 - Alex Aparin
也许你应该解释一下“但它们似乎没有起作用”,因为它们真的应该起作用。(“它们”是指谁?) - Jongware
但是CodeBlocks无法识别该库,尽管我已经正确安装了libpng和pngwriter目录。 - busssard
4个回答

13

考虑使用Netpbm格式(pbm、pgm或ppm)。

这些图像是极其简单的文本文件,您可以在不使用任何特殊库的情况下编写。然后使用一些第三方软件,如ImageMagick、GraphicsMagick或pnmtopng将您的图像转换为PNG格式。这里有一篇维基文章描述了Netpbm格式

这是一个简单的PPM图像:

P3 2 3 255
0 0 0       255 255 255
255 0 0     0 255 255
100 100 100 200 200 200

第一行包含“P3”(标识为文本-PPM的“魔数”),2(宽度),3(高度),255(最大强度)。 第二行包含顶部两个RGB像素。 第三行和第四行分别包含第2行和第3行的两个RGB像素。

如果需要更大范围的强度,最大强度(例如1024)可以使用更大的数字,最高可达65535。

从这一点开始由Mark Setchell编辑 - 所以我是罪魁祸首!

图像看起来像这样(当六个像素被放大时):

enter image description here

ImageMagick的转换和放大命令如下:

convert image.ppm -scale 400x result.png

如果ImageMagick有些沉重或安装困难,您可以更简单地使用NetPBM工具(从这里)像这样(它是一个单独的预编译二进制文件)。
pnmtopng image.ppm > result.png

1
@busssard我已经添加了一张图片,以便更容易地进行可视化 - 希望你不介意,格伦。现在你可以看到颜色确实是可能的,因为每个像素都有红色、绿色和蓝色值 - 所以你可以实现1600万种颜色... 红色从0到255,绿色从0到255,蓝色从0到255。如果你想要更多,你可以将第一行的“255”改为65535,并使用3个值来表示红色、绿色和蓝色,每个值的范围为0到65535。 - Mark Setchell
2
如果您想查看一些实际代码来给您一些线索,您可以在这里查看我的答案并进行适应... https://dev59.com/G33aa4cB1Zd3GeqPjeeS#22580958 - Mark Setchell
我非常感谢您的阐述! 那么 fputc(pixel, imagefile) 是魔术命令吗? pixel 可以是一个数组吗?根据 Glenns 的回答所提供的信息? - busssard
1
如果您的图像是彩色的,您必须使用“PPM”格式,这意味着文件必须以“P3”或“P6”开头。如果您使用“P3”,则要在ASCII(人类可读的数字,正如Glenn的例子中所示)中编写数据,在“C / C ++”编程中,您将使用“printf“%d ”,pix [0]”。如果您使用“P6”,它仍然是“PPM”,但是您会使用“fputc()”以二进制形式编写文件,它会写入单个无符号字符(字节)。二进制(“P6”)的优点是文件大小约为3-4倍小。但是ImageMagick和“pnmtopng”都将理解“P3”和“P6”。 - Mark Setchell
如果您想一次性编写整个数组,您需要确保它按正确的顺序排列,即R、G、B、R、G、B、R、G、B... 如果是这样,您可以像我链接的示例中那样编写标题,然后使用fwrite(pix,1,<numelements>,stream)来编写实际像素。如果您的数据在内存中没有按正确的顺序排列,您可能需要逐个拾取单个字节,并使用fputc()输出它们。 - Mark Setchell
显示剩余3条评论

11

如果你已经安装了 Magick++ 并且愿意使用它,你可以像这样用 C/C++ 编写代码:

////////////////////////////////////////////////////////////////////////////////
// sample.cpp
// Mark Setchell
//
// ImageMagick Magick++ sample code
//
// Compile with:
// g++ sample.cpp -o sample $(Magick++-config --cppflags --cxxflags --ldflags --libs)
////////////////////////////////////////////////////////////////////////////////
#include <Magick++.h> 
#include <iostream> 

using namespace std; 
using namespace Magick; 

int main(int argc,char **argv) 
{ 
   unsigned char pix[]={200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};

   // Initialise ImageMagick library
   InitializeMagick(*argv);

   // Create Image object and read in from pixel data above
   Image image; 
   image.read(2,3,"RGB",CharPixel,pix);

   // Write the image to a file - change extension if you want a GIF or JPEG
   image.write("result.png"); 
}

输入图像描述


哇,这真的让它变得更容易了,我会尝试让它运行。 - busssard
我可以将.ppm转换为.png,但只能在线完成。你在哪里找到这个Magick++?我找不到任何下载链接,也可能不知道需要下载哪一个版本。我使用的是Windows 10,64位系统。谢谢。 - George_E -old
1
Magick++是一个库,您需要下载它,然后可以与您的编译器一起使用。 - busssard

7

你离正确只差一步之遥-尝试得很好!据我所见,您只犯了几个错误:

  1. 如果以二进制书写,则需要使用P6而不是P3

  2. 对于8位数据,您需要使用unsigned char而不是int类型。

  3. 您颠倒了宽度和高度。

  4. 您正在使用PGM扩展名,该扩展名适用于便携式灰度地图,而您的数据是彩色的,因此需要使用PPM扩展名,该扩展名适用于便携式像素图像。

所以,正确的代码应该是这样的:

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

int main(){
   FILE *imageFile;
   int x,y,pixel,height=3,width=2;

   imageFile=fopen("image.ppm","wb");
   if(imageFile==NULL){
      perror("ERROR: Cannot open output file");
      exit(EXIT_FAILURE);
   }

   fprintf(imageFile,"P6\n");               // P6 filetype
   fprintf(imageFile,"%d %d\n",width,height);   // dimensions
   fprintf(imageFile,"255\n");              // Max pixel

   unsigned char pix[]={200,200,200, 100,100,100, 0,0,0, 255,0,0, 0,255,0, 0,0,255};
   fwrite(pix,1,18,imageFile);
   fclose(imageFile);
}

如果您运行该命令,您可以将生成的图像转换为漂亮的大PNG格式:

convert image.ppm -scale 400x result.png

在此输入图片描述

如果您随后需要16位数据,则应将255更改为65535,并将其存储在unsigned short数组中,而不是unsigned char,当您进行fwrite()时,您需要写入两倍的字节数。


最初可以尝试使用来自Sourceforge的NetPBM库中的pnmtopng,就像我在Glenn答案底部展示的那样,因为它更小且更易于安装。除非您使用的是Linux,否则只需使用convert命令即可,该命令是ImageMagick的一部分,可能已经安装。您使用的平台是什么? - Mark Setchell
我使用Ubuntu,我认为ImageMagick已经安装了,但是我的编译器找不到Magick++.h,所以我不确定... - busssard
如果你有并且想要使用Magick ++,答案会完全不同!Glenn(和我)都认为你想要最简单的方法,这就是我们两个都回答的方式。如果您想要使用Magick ++,则需要安装magick ++ -dev软件包,并编写完全不同的代码以及进行不同的编译和链接。不过,您将获得更清洁的项目。也许最好通过电子邮件聊天。 - Mark Setchell
回答你的另一个问题,不会只是填满。如果你说这张图片是10x20,每个像素3字节,它会期望600字节。 - Mark Setchell
你已经在命令行中将PPM转换为PNG了吗?如果是这样,你可以在关闭()后添加对“system()”的调用来自动执行该程序,类似于system("convert image.ppm image.png"); - Mark Setchell
显示剩余3条评论

5
下面的代码将以整数数组形式接收像素颜色的输入,并将其写入一个 .bmp 位图文件,或者反过来,读取一个 .bmp 位图文件并将其图像内容存储为 int 数组。它只需要使用 <fstream> 库。输入参数 path 可以是例如 C:/path/to/your/image.bmp,而 data 的格式为 data[x+y*width]=(red<<16)|(green<<8)|blue;,其中 redgreenblue 是介于 0-255 范围内的整数,像素位置为 (x,y)
#include <string>
#include <fstream>
using namespace std;
typedef unsigned int uint;
int* read_bmp(const string path, uint& width, uint& height) {
    ifstream file(path, ios::in|ios::binary);
    if(file.fail()) println("\rError: File \""+filename+"\" does not exist!");
    uint w=0, h=0;
    char header[54];
    file.read(header, 54);
    for(uint i=0; i<4; i++) {
        w |= (header[18+i]&255)<<(8*i);
        h |= (header[22+i]&255)<<(8*i);
    }
    const int pad=(4-(3*w)%4)%4, imgsize=(3*w+pad)*h;
    char* img = new char[imgsize];
    file.read(img, imgsize);
    file.close();
    int* data = new int[w*h];
    for(uint y=0; y<h; y++) {
        for(uint x=0; x<w; x++) {
            const int i = 3*x+y*(3*w+pad);
            data[x+(h-1-y)*w] = (img[i]&255)|(img[i+1]&255)<<8|(img[i+2]&255)<<16;
        }
    }
    delete[] img;
    width = w;
    height = h;
    return data;
}
void write_bmp(const string path, const uint width, const uint height, const int* const data) {
    const int pad=(4-(3*width)%4)%4, filesize=54+(3*width+pad)*height; // horizontal line must be a multiple of 4 bytes long, header is 54 bytes
    char header[54] = { 'B','M', 0,0,0,0, 0,0,0,0, 54,0,0,0, 40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,24,0 };
    for(uint i=0; i<4; i++) {
        header[ 2+i] = (char)((filesize>>(8*i))&255);
        header[18+i] = (char)((width   >>(8*i))&255);
        header[22+i] = (char)((height  >>(8*i))&255);
    }
    char* img = new char[filesize];
    for(uint i=0; i<54; i++) img[i] = header[i];
    for(uint y=0; y<height; y++) {
        for(uint x=0; x<width; x++) {
            const int color = data[x+(height-1-y)*width];
            const int i = 54+3*x+y*(3*width+pad);
            img[i  ] = (char)( color     &255);
            img[i+1] = (char)((color>> 8)&255);
            img[i+2] = (char)((color>>16)&255);
        }
        for(uint p=0; p<pad; p++) img[54+(3*width+p)+y*(3*width+pad)] = 0;
    }
    ofstream file(path, ios::out|ios::binary);
    file.write(img, filesize);
    file.close();
    delete[] img;
}

这段代码的灵感来源于 https://dev59.com/YHE85IYBdhLWcg3wr1ux#47785639
对于 .png 图像,请使用lodepng.cpplodepng.h
#include <string>
#include <vector>
#include <fstream>
#include "lodepng.h"
using namespace std;
typedef unsigned int uint;
int* read_png(const string path, uint& width, uint& height) {
    vector<uchar> img;
    lodepng::decode(img, width, height, path, LCT_RGB);
    int* data = new int[width*height];
    for(uint i=0; i<width*height; i++) {
        data[i] = img[3*i]<<16|img[3*i+1]<<8|img[3*i+2];
    }
    return data;
}
void write_png(const string path, const uint width, const uint height, const int* const data) {
    uchar* img = new uchar[3*width*height];
    for(uint i=0; i<width*height; i++) {
        const int color = data[i];
        img[3*i  ] = (color>>16)&255;
        img[3*i+1] = (color>> 8)&255;
        img[3*i+2] =  color     &255;
    }
    lodepng::encode(path, img, width, height, LCT_RGB);
    delete[] img;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接