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

busssard
  • 158
  • 1
  • 1
  • 11
  • 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: http://doc.qt.io/qt-5/qpixmap.html#loadFromData. Output png file can be saved with save method (http://doc.qt.io/qt-5/qpixmap.html#save) – Alex Aparin Mar 29 '16 at 15:47
  • Perhaps you should explain "but they do not seem to be working" because they really ought to. (Who are "they"?) – Jongware Mar 29 '16 at 15:49
  • but codeblocks does not recognize the library, even though i properly installed libpng and the pngwriter drectories – busssard Mar 29 '16 at 16:13

4 Answers4

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
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
Glenn Randers-Pehrson
  • 11,940
  • 3
  • 37
  • 61
  • 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? – busssard Mar 29 '16 at 16:26
  • 1
    @busssard 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. – Mark Setchell Mar 30 '16 at 08:53
  • 2
    @busssard If you want to see some actual code to give you a clue, you can look at my answer here and adapt... http://stackoverflow.com/a/22580958/2836621 – Mark Setchell Mar 30 '16 at 09:06
  • 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? – busssard Mar 30 '16 at 10:05
  • 1
    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`. – Mark Setchell Mar 30 '16 at 10:12
  • 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,,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()`. – Mark Setchell Mar 30 '16 at 10:15
  • 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. – Mark Setchell Mar 30 '16 at 11:15
  • 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... :D – busssard Apr 02 '16 at 12:29
10

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

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • wow, that really makes it so much easier, i will try to get it running. – busssard Apr 05 '16 at 20:35
  • 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. Thanks – George_E -old Feb 25 '18 at 14:54
  • 1
    magick++ is a library you have to download and then can use with your compiler – busssard Feb 20 '19 at 12:51
6

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.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • 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? – Mark Setchell Apr 02 '16 at 14:42
  • i use Ubuntu, i thought ImageMagick was installed, but my compiler does not find `Magick++.h` so i am unsure... – busssard Apr 02 '16 at 15:22
  • one more thing. If the array is smaller than the defined area, then it just fills up the remaining pixels with random values? – busssard Apr 02 '16 at 15:32
  • 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 Email – Mark Setchell Apr 02 '16 at 16:04
  • 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. – Mark Setchell Apr 02 '16 at 16:08
  • 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");` – Mark Setchell Apr 02 '16 at 16:12
  • 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. – busssard Apr 02 '16 at 16:24
  • 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.` – busssard Apr 02 '16 at 16:44
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://stackoverflow.com/a/47785639/9178992

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;
}
ProjectPhysX
  • 4,535
  • 2
  • 14
  • 34