C++ create png/bitmap from array of numbers
Asked Answered
P

4

10

So i found this link regarding my question, but it is for c#

Create a PNG from an array of bytes

I have a variable int array of numbers. i will call it "pix[ ]" for now it can be any size from 3 to 256, later possibly bigger.

What i want to do now, is to convert it into a pixel image. I am still a noobin c++ so pleas excuse me. I tried to download some libaries that make working with libpng easier, but they do not seem to be working (ubuntu, code::blocks) So i have questions in the following:

1) how do you create a new bitmap (which libaries, which command)?

2) how do i fill it with information from "pix[ ]" ?

3) how do i save it?

if it is a repost of a question i am happy about a link also ;)

Here is what i worked out so far, thanks for your help.

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);
}

i have not fully understood what it does. i can open the output image, but it is not a correct representation of the Array.

If i change things around, for example making a 2 dimensional array, then the image viewer tells me "expected an integer" and doesn't show me an image.

So far so good. As i have the array before the image i created a function aufrunden to round up to the next int number because i want to create a square image.

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

This function is used in the creation of the image. If the image is bigger than the information the array provides like this (a is the length of th array)

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

It might happen now, that the array is a=24 long. aufrunden makes the image 3x3 so it has 27 values...meaning it is missing the values for 1 pixel. Or worse it is only a=23 long. also creating a 3x3 image.

What will fwrite(pix,1,18,imageFile); write in those pixels for information? It would be best if the remaing values are just 0.

*edit never mind, i will just add 0 to the end of the array until it is filling up the whole square...sorry

Pyridine answered 29/3, 2016 at 15:18 Comment(3)
If you need only result png file, you can use Qt framework. It contains appropriate QPixmap class, which has useful loadFromData method. It is additional ref: doc.qt.io/qt-5/qpixmap.html#loadFromData. Output png file can be saved with save method (doc.qt.io/qt-5/qpixmap.html#save)Abomb
Perhaps you should explain "but they do not seem to be working" because they really ought to. (Who are "they"?)Hulsey
but codeblocks does not recognize the library, even though i properly installed libpng and the pngwriter drectoriesPyridine
B
13

Consider using a Netpbm format (pbm, pgm, or ppm).

These images are extremely simple text files that you can write without any special libraries. Then use some third-party software such as ImageMagick, GraphicsMagick, or pnmtopng to convert your image to PNG format. Here is a wiki article describing the Netpbm format.

Here's a simple PPM image:

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

The first line contains "P3" (the "magic number identifying it as a text-PPM), 2 (width), 3 (height), 255 (maximum intensity). The second line contains the two RGB pixels for the top row. The third and fourth lines each contain the two RGB pixels for rows 2 and 3.

Use a larger number for maximum intensity (e.g. 1024) if you need a larger range of intensities, up to 65535.

Edited by Mark Setchell beyond this point - so I am the guilty party!

The image looks like this (when the six pixels are enlarged):

enter image description here

The ImageMagick command to convert, and enlarge, is like this:

convert image.ppm -scale 400x result.png

If ImageMagick is a bit heavyweight, or difficult to install you can more simply use the NetPBM tools (from here) like this (it's a single precompiled binary)

pnmtopng image.ppm > result.png
Boozer answered 29/3, 2016 at 16:21 Comment(8)
nice but hang on, so when creating it what is the syntax, i cannot read it from the wiki page. also is there a way to create image files pixel by pixel with more than 255 colors?Pyridine
@Pyridine I have added a picture to make it easier to visualise - I hope you don't mind, Glenn. You can now see that colour is indeed possible, since there is a Red, Green and Blue value for each pixel - so you can achieve 16 million colours... 0 to 255 for Red, 0 to 255 for Green and 0 to 255 for Blue. If you want even more, you can change the 255 on the first line to 65535 and use 3 values for Red, Green and Blue each in the range 0 to 65535.Irradiant
@Pyridine If you want to see some actual code to give you a clue, you can look at my answer here and adapt... https://mcmap.net/q/1160662/-writing-a-png-in-cIrradiant
i really appreciate the elaboration! so fputc(pixel, imagefile) is the magic command? Can pixel be an array? with the information given in Glenns answer?Pyridine
I your image is colour, you must use PPM which means the file must start with P3 or P6. If you use P3, you write the data in ASCII (human readable numbers exactly like Glenn's example) and in C/C++ programming you would use printf "%d ",pix[0]. If you use P6, it is still PPM but you write the file in binary using fputc() which writes a single unsigned character (byte). The advantage of binary (P6) is that the file is around 3-4 times smaller. But ImageMagick and pnmtopng will understand both P3 and P6.Irradiant
If you want to write your entire array in one go, you need to be sure that it is in the correct order, R, G, B, R, G, B, R, G, B... and if it is, you can write the header like in my linked example and then use fwrite(pix,1,<numelements>,stream) to write the actual pixels. If your data is not in the correct order in memory, you may have to pick up the individual bytes one at a time and output them with fputc().Irradiant
If you are still stuck, please click edit under your original question and show how your array will look (in real code terms) for a 2x3 image like Glenn's example.Irradiant
ok @mark i edited my original question. I have not fully understood what fwrite and fputc actually do. and my code does not work either... :DPyridine
I
11

If, as it seems, you have got Magick++ and are happy to use that, you can write your code in C/C++ like this:

////////////////////////////////////////////////////////////////////////////////
// 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"); 
}

enter image description here

Irradiant answered 5/4, 2016 at 17:30 Comment(3)
wow, that really makes it so much easier, i will try to get it running.Pyridine
I can convert .ppm to .png, but only online. Where did you find this Magick++? I cannot find any downloads, and might not know which one I need to download anyway. I am running Windows 10, 64 bit. ThanksMaurita
magick++ is a library you have to download and then can use with your compilerPyridine
I
7

You are not far off - well done for trying! As far as I can see, you only had a couple of mistakes:

  1. You had P3 where you would actually need P6 if writing in binary.

  2. You were using int type for your data, whereas you need to be using unsigned char for 8-bit data.

  3. You had the width and height interchanged.

  4. You were using the PGM extension which is for Portable Grey Maps, whereas your data is colour, so you need to use the PPM extension which is for Portable Pix Map.

So, the working code looks like this:

#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);
}

If you then run that, you can convert the resulting image to a nice big PNG with

convert image.ppm -scale 400x result.png

enter image description here

If you subsequently need 16-bit data, you would change the 255 to 65535, and store in an unsigned short array rather than unsigned char and when you come to the fwrite(), you would need to write double the number of bytes.

Irradiant answered 2/4, 2016 at 13:50 Comment(8)
Try initially with pnmtopng from the NetPBM library from Sourceforge how I showed at the bottom of Glenn's answer as it is much smaller and easier to install. Unless you are on Linux, in which case, just use the convert command which is part of ImageMagick and probably already installed. What platform are you on?Irradiant
i use Ubuntu, i thought ImageMagick was installed, but my compiler does not find Magick++.h so i am unsure...Pyridine
one more thing. If the array is smaller than the defined area, then it just fills up the remaining pixels with random values?Pyridine
If you have, and want to use, Magick++, the answer would be completely different! Glenn (and I) both thought you wanted the "easiest" way which is how we both answered. If you want to use Magick++, you need to install the magick++-dev package and write completely different code and compile and link differently. You'll get a cleaner project that way though. Might be better to chat via EmailIrradiant
In answer to your other question, no it doesn't just fill up. If you say the image is 10x20 and 3 byes per pixel, it will expect 600 bytes.Irradiant
Have you got the conversion from PPM to PNG working at the commandline yet? If so, you can automate that in your program by adding a call to system() after the close(), something like system("convert image.ppm image.png");Irradiant
Hey mark, i would love to understand what i am doing. As i want to read out the information i put in i guess imageMagick is the way to go anyways... i will install it i guess.Pyridine
i installed magick++-dev but the system("convert...") does not work. it tells me: convert.im6:unable to read image data 'image.ppm' @error/pnm.c/ReadPNMImage/899. and convert.im6:no images defined 'image.png' @error/convert.c/ConvertImageCommand/3044.Pyridine
P
5

The code below will take an integer array of pixel colors as input and write it to a .bmp bitmap file or, in reverse, read a .bmp bitmap file and store its image contents as an int array. It only requires the <fstream> library. The input parameter path can be for example C:/path/to/your/image.bmp and data is formatted as data[x+y*width]=(red<<16)|(green<<8)|blue;, whereby red, green and blue are integers in the range 0-255 and the pixel position is (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;
}

The code snippet was inspired by https://mcmap.net/q/319859/-writing-bmp-image-in-pure-c-c-without-other-libraries

For .png images, use lodepng.cpp and lodepng.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;
}
Promotive answered 15/10, 2019 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.