C++ create png/bitmap from array of numbers
Solution 1
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):
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
Solution 2
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");
}
Solution 3
You are not far off - well done for trying! As far as I can see, you only had a couple of mistakes:
You had
P3
where you would actually needP6
if writing in binary.You were using
int
type for your data, whereas you need to be usingunsigned char
for 8-bit data.You had the width and height interchanged.
You were using the
PGM
extension which is for Portable Grey Maps, whereas your data is colour, so you need to use thePPM
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
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.
Solution 4
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 uint 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;
}
busssard
Updated on December 04, 2020Comments
-
busssard over 3 years
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 onlya=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 about 8 yearsnice 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?
-
Mark Setchell about 8 years@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 about 8 years@busssard If you want to see some actual code to give you a clue, you can look at my answer here and adapt... stackoverflow.com/a/22580958/2836621
-
busssard about 8 yearsi really appreciate the elaboration! so
fputc(pixel, imagefile)
is the magic command? Canpixel
be an array? with the information given in Glenns answer? -
Mark Setchell about 8 yearsI your image is colour, you must use
PPM
which means the file must start withP3
orP6
. If you useP3
, you write the data in ASCII (human readable numbers exactly like Glenn's example) and inC/C++
programming you would useprintf "%d ",pix[0]
. If you useP6
, it is stillPPM
but you write the file in binary usingfputc()
which writes a single unsigned character (byte). The advantage of binary (P6
) is that the file is around 3-4 times smaller. But ImageMagick andpnmtopng
will understand bothP3
andP6
. -
Mark Setchell about 8 yearsIf 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 withfputc()
. -
Mark Setchell about 8 yearsIf 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. -
busssard about 8 yearsok @mark i edited my original question. I have not fully understood what
fwrite
andfputc
actually do. and my code does not work either... :D -
Mark Setchell about 8 yearsTry initially with
pnmtopng
from theNetPBM
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 theconvert
command which is part of ImageMagick and probably already installed. What platform are you on? -
busssard about 8 yearsi use Ubuntu, i thought ImageMagick was installed, but my compiler does not find
Magick++.h
so i am unsure... -
busssard about 8 yearsone more thing. If the array is smaller than the defined area, then it just fills up the remaining pixels with random values?
-
Mark Setchell about 8 yearsIf 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 about 8 yearsIn 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 about 8 yearsHave 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 theclose()
, something likesystem("convert image.ppm image.png");
-
busssard about 8 yearsHey 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 about 8 yearsi installed
magick++-dev
but thesystem("convert...")
does not work. it tells me:convert.im6:unable to read image data 'image.ppm' @error/pnm.c/ReadPNMImage/899.
andconvert.im6:no images defined 'image.png' @error/convert.c/ConvertImageCommand/3044.
-
busssard about 8 yearswow, that really makes it so much easier, i will try to get it running.
-
George_E -old about 6 yearsI can convert
.ppm
to.png
, but only online. Where did you find thisMagick++
? I cannot find any downloads, and might not know which one I need to download anyway. I am running Windows 10, 64 bit. Thanks -
busssard about 5 yearsmagick++ is a library you have to download and then can use with your compiler