Save OpenGL screen pixels to PNG using libpng - c++

I've been using SOIL to save images as BMP, but it turns out that SOIL (or stbi to be more specific) saves ~5MB images (which is about 1366x768 resolution image or more) which is quite insane.
Original BMP saving code (NOTE Everything is done in the render function):
uint8_t *pixels = new uint8_t[w * h * 3];
// copy pixels from screen
glBindTexture(GL_TEXTURE_2D, screenTex);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
// invert pixels (stolen from SOILs source code)
for (int j = 0; j * 2 < h; ++j) {
int x = j * w * 3;
int y = (h - 1 - j) * w * 3;
for (int i = w * 3; i > 0; --i) {
uint8_t tmp = pixels[x];
pixels[x] = pixels[y];
pixels[y] = tmp;
++x;
++y;
}
}
// save the image
int err = SOIL_save_image(fileName, SOIL_SAVE_TYPE_BMP, w, h, 3, pixels);
if (err)
printf("Done\n");
else
printf("Failed\n");
Code for saving PNG:
bool save_png_libpng(const char *filename, uint8_t *pixels, int w, int h)
{
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, &info);
return false;
}
FILE *fp = fopen(filename, "wb");
if (!fp) {
png_destroy_write_struct(&png, &info);
return false;
}
png_init_io(png, fp);
png_set_IHDR(png, info, w, h, 8 /* depth */, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_colorp palette = (png_colorp)png_malloc(png, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
if (!palette) {
fclose(fp);
png_destroy_write_struct(&png, &info);
return false;
}
png_set_PLTE(png, info, palette, PNG_MAX_PALETTE_LENGTH);
png_write_info(png, info);
png_set_packing(png);
png_bytepp rows = (png_bytepp)png_malloc(png, h * sizeof(png_bytep));
for (int i = 0; i < h; ++i)
rows[i] = (png_bytep)(pixels + (h - i) * w * 3);
png_write_image(png, rows);
png_write_end(png, info);
png_free(png, palette);
png_destroy_write_struct(&png, &info);
fclose(fp);
delete[] rows;
return true;
}
NOTE: I have not changed any of the original code, just replaced SOIL_save_image with save_png.
The code fails in the following line:
png_write_image(png, rows)
in PNG's source code, this function fails at the highlighted line:
void PNGAPI
png_write_image(png_structrp png_ptr, png_bytepp image)
{
png_uint_32 i; /* row index */
int pass, num_pass; /* pass variables */
png_bytepp rp; /* points to current row */
if (png_ptr == NULL)
return;
png_debug(1, "in png_write_image");
#ifdef PNG_WRITE_INTERLACING_SUPPORTED
/* Initialize interlace handling. If image is not interlaced,
* this will set pass to 1
*/
num_pass = png_set_interlace_handling(png_ptr);
#else
num_pass = 1;
#endif
/* Loop through passes */
for (pass = 0; pass < num_pass; pass++)
{
/* Loop through image */
for (i = 0, rp = image; i < png_ptr->height; i++, rp++)
{
png_write_row(png_ptr, *rp); // HERE
}
}
}
png_write_row then fails here: (The code for png_write_row is quite long to post here, so if you're curious about what happens before this line, you can check out pngwrite.c in png's source code. )
/* Copy user's row into buffer, leaving room for filter byte. */
memcpy(png_ptr->row_buf + 1, row, row_info.rowbytes);
P.S: I was using exactly the same code on MinGW and it was working 100% fine, when I switched to MSVC it started failing. I'm not sure if GCC does something magically here or it's my code's fault, so I would like to know for the sake of learning.

The following line:
rows[i] = (png_bytep)(pixels + (h - i) * w * 3);
is unforunately going past the block of memory (pixels), so the following edit fixes it:
rows[i] = (png_bytep)(pixels + (h - i - 1) * w * 3);
Quite trivial but whatever.

Related

How can I use XCopyArea with colors other than blue in Xlib?

I'm trying to build a ray tracer in C++. To that end, I'd like to draw the final image to a window (instead of just outputting to PPM, which is what I'm currently doing). I have some code below that saves the color of each pixel to an array that matches the size of an image and then attempts to display that image using Xlib. This code snippet below allocates data to a 100 x 100 array image:
int width = 100;
int height = 100;
int data[width * height * 4];
for (int i = 0; i < width * height; i++){
data[i] = 255;
}
XImage *image = XCreateImage(d, DefaultVisual(d, 0), 24, ZPixmap, 0, (char*) data, 100, 100, 32, 0);
Pixmap pm = XCreatePixmap(d, w, 100, 100, 24);
XPutImage(d, pm, gc, image, 0,0,0,0, 100, 100);
This outputs a nice blue square in my window. But, if I change the for loop in the above example to:
for (int i = 0; i < width * height; i+= 4){
data[i] = 255;
data[i + 1] = 255;
data[i + 2] = 0;
data[i + 3] = 0;
}
I still get blue with some black mixed in, but I expected yellow. What am I doing wrong here?
Full code below for reference:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
Display *d;
Window w;
XEvent e;
const char *msg = "Hello, World!";
int s;
d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Cannot open display\n");
exit(1);
}
s = DefaultScreen(d);
w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 500, 500, 1,
BlackPixel(d, s), WhitePixel(d, s));
GC gc = XCreateGC(d, w, 0, NULL);
XSelectInput(d, w, ExposureMask | KeyPressMask);
XMapWindow(d, w);
int width = 100;
int height = 100;
int data[width * height * 4];
for (int i = 0; i < width * height; i++){
data[i] = 255;
}
XImage *image = XCreateImage(d, DefaultVisual(d, 0), 24, ZPixmap, 0, (char*) data, 100, 100, 32, 0);
Pixmap pm = XCreatePixmap(d, w, 100, 100, 24);
XPutImage(d, pm, gc, image, 0,0,0,0, 100, 100);
while (1) {
XNextEvent(d, &e);
if (e.type == Expose) {
XCopyArea(d, pm, w, gc, 0, 0, width, height, 10, 10);
}
if (e.type == KeyPress)
break;
}
XCloseDisplay(d);
return 0;
}
Finally figured it out. If I remember where I found the original sources for some of this stuff, I'll link it in an edit. But basically below is a working for loop. I'm not entirely sure why I need it, but after every RGB triplet, we need a byte of 0x00. This is on my computer where I have 24-bit true color depth (which is probably the same as every modern computer). So if you have 24 bits, then you have 3 bytes on every line which, for many reasons that I don't know, is illegal. If someone could explain this to me, I'd really love to know. The code is below:
unsigned char data[width * height * 4];
for (int i = 0; i < width * height * 4; i+=4){
data[i] = 255; // blue channel
data[i + 1] = 0; // green channel
data[i + 2] = 255; // red channel
data[i + 3] = 0; // dead pixel that is always zero
}

glTexImage2D slicing and alignment issues appearing in window

I'm working on making my own topographic map, and I have been using .hgt files from NASA.
I'm loading the files using
void MapImage::load_map_file(const char* filename) {
std::ifstream file(filename, std::ios::in | std::ios::binary);
if (!file) {
std::cout << "Error opening file!" << std::endl;
}
std::vector<short> tempHeight(TOTAL_SIZE);
unsigned char buffer[2];
int x, y;
for (int i = 0; i < TOTAL_SIZE; i++) {
if (!file.read(reinterpret_cast<char*>(buffer), sizeof(buffer))) {
std::cout << "Error reading file!" << std::endl;
}
tempHeight[i] = (buffer[0] << 8) | buffer[1];
}
height = tempHeight;
}
And then adding them to an inmemory bitmap using:
void MapImage::loadTextureImage() {
img_tex = 0;
glGenTextures(1, &img_tex);
int x, y, w, h;
w = h = SRTM_SIZE;
unsigned char* img;
img = (unsigned char *)malloc(3 * w * h);
memset(img, 0, sizeof(img));
int g = 0;
double height_color;
/*
for(int i = 0; i < TOTAL_SIZE; i++){
height_color = (float)height[i] / 10.0;
g = (height_color * 255);
if (g>255)g = 255;
img[i * 3 + 2] = (unsigned char)0;
img[i * 3 + 1] = (unsigned char)g;
img[i * 3 + 0]= (unsigned char)0;
}
*/
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; ++j) {
x = i;
y = (h - 1) - j;
height_color = (float)height[j + (i * w)] / 10.0;
g = (height_color * 255);
if (g>255)g = 255;
img[(x + y*w) * 3 + 2] = (unsigned char)0;
img[(x + y*w) * 3 + 1] = (unsigned char)g;
img[(x + y*w) * 3] = (unsigned char)0;
}
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, img_tex);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGB,
w,
h,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
img
);
}
However this results in a image with the corner sliced, like this
Using the commented version in the loadTextureImage() looks slightly different, however with the same corner sliced.
Does anyone have a hint to whats going on? I've tried using a image as a texture, loading with the stbi library, and that works fine, so I'm not sure where it's going wrong.
(the coordinates for the image is N10E099)
This looks like row misalignment, caused by the 3-wide colour data. Try using the following call just before glTexImage2D:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
This alignment value, which is 4 by default, is used by glTexImage2D and friends whenever texture data is read to be sent to the GPU.
There is no verification that it matches what the data actually looks like, so in cases like yours where a row doesn't end on a 4-byte boundary, the first few bytes of the next row will be skipped, leading to this diagonal distortion.
Texture data transfers in the other direction (from the GPU to client memory) are aligned via glPixelStorei(GL_PACK_ALIGNMENT, 1);.

LibQREncode qrcode to BMP

I'm creating a qrcode with the library qrencode.h
This creation is working nice but how would one output the qrcode to a BMP file within c++?
At this very moment i have this code:
const char* szSourceSring = QRCODE_TEXT;
unsigned int unWidth, x, y, l, n, unWidthAdjusted, unDataBytes;
unsigned char* pRGBData, *pSourceData, *pDestData;
QRcode* pQRC;
FILE* f;
if (pQRC = QRcode_encodeString(szSourceSring, 4, QR_ECLEVEL_H, QR_MODE_8, 1))
{
unWidth = pQRC->width;
unWidthAdjusted = unWidth * OUT_FILE_PIXEL_PRESCALER * 3;
if (unWidthAdjusted % 4)
unWidthAdjusted = (unWidthAdjusted / 4 + 1) * 4;
unDataBytes = unWidthAdjusted * unWidth * OUT_FILE_PIXEL_PRESCALER;
// Allocate pixels buffer
if (!(pRGBData = (unsigned char*)malloc(unDataBytes)))
{
printf("Out of memory");
}
// Preset to white
memset(pRGBData, 0xff, unDataBytes);
// Prepare bmp headers
BITMAPFILEHEADER kFileHeader;
kFileHeader.bfType = 0x4D42; // "BM"
kFileHeader.bfSize = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER) +
unDataBytes;
kFileHeader.bfReserved1 = 0;
kFileHeader.bfReserved2 = 0;
kFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER kInfoHeader;
kInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
kInfoHeader.biWidth = unWidth * OUT_FILE_PIXEL_PRESCALER;
kInfoHeader.biHeight = -((int)unWidth * OUT_FILE_PIXEL_PRESCALER);
kInfoHeader.biPlanes = 1;
kInfoHeader.biBitCount = 24;
kInfoHeader.biCompression = BI_RGB;
kInfoHeader.biSizeImage = 0;
kInfoHeader.biXPelsPerMeter = 0;
kInfoHeader.biYPelsPerMeter = 0;
kInfoHeader.biClrUsed = 0;
kInfoHeader.biClrImportant = 0;
// Convert QrCode bits to bmp pixels
pSourceData = pQRC->data;
for(y = 0; y < unWidth; y++)
{
pDestData = pRGBData + unWidthAdjusted * y * OUT_FILE_PIXEL_PRESCALER;
for(x = 0; x < unWidth; x++)
{
if (*pSourceData & 1)
{
for(l = 0; l < OUT_FILE_PIXEL_PRESCALER; l++)
{
for(n = 0; n < OUT_FILE_PIXEL_PRESCALER; n++)
{
*(pDestData + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_B;
*(pDestData + 1 + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_G;
*(pDestData + 2 + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_R;
}
}
}
pDestData += 3 * OUT_FILE_PIXEL_PRESCALER;
pSourceData++;
}
}
// Output the bmp file
/*if (((f = fopen(OUT_FILE, "r")) != NULL))
{*/
f = fopen(OUT_FILE, "wb");
fwrite(&kFileHeader, sizeof(BITMAPFILEHEADER), 14, f);
fwrite(&kInfoHeader, sizeof(BITMAPINFOHEADER), 40, f);
fwrite(pRGBData, sizeof(unsigned char), unDataBytes, f);
fclose(f);
/* }
else
{
printf("Unable to open file");
}
*/
// Free data
free(pRGBData);
QRcode_free(pQRC);
}
else
{
printf("NULL returned");
}
But somehow this creates a BMP with corrupt headers. Whenever i'm opening the bmp file it says:
"BMP Image has unsupported header size"
What am i doing wrong?
And is it possible to save to png instead of BMP?
I have access to the libPNG library
Here is a code example which dumps a 24 bpp bmp file created from a QR-Code. The error you see is probably not caused by the QR-Code library, but rather something in the bmp file code.
The bmp file created by this example works fine with the image viewer packaged with my Windows 8.1. If you also do not see the error, you could check for differences in each binary output to pinpoint the problem. If you want.
This question is tagged "C++" and "C++11", so this example uses the C++ std library for file output, and doesn't use malloc. (But almost equally bad -- I use new and delete in some container code, where a std::vector member is preferred...don't tell anyone). Also, this example writes each piece of data directly to the file, instead of using a file-sized intermediate buffer, like pDestData.
#include <iostream>
#include <fstream>
// A fake (or "somewhat limited") QR Code data container
struct Qrc {
int dimsize; // the width and height
unsigned char* data; // buffer which contains the elements
Qrc() {
static const unsigned int bin[] = { // encodes an important secret message
0xfc8b7d7f,0xa801a83,0xd6e54d76,0xaa9eb2ed,0x43ed05db,0xb8786837,0x55555fe0,
0x5a4c807f,0xcf315c00,0x6e8019ce,0xc7819e0d,0xd4857ba8,0x4ac5e347,0xf6f349ba,
0xd433ccdd,0x2998361e,0x4453fab3,0x526d9085,0x81f38924,0xb4da0811,0x84b3131a,
0x9639915e,0x3b74a4ff,0x42aa0c11,0x4127be16,0x1f4350,0xff620296,0xad54de1,
0xd38c2272,0xa3f76155,0x5366a7ab,0x9bdd2257,0x300d5520,0x85842e7f,0 };
dimsize = 33;
data = new unsigned char[dimsize * dimsize];
auto p = data;
auto endp = p + dimsize * dimsize;
for(unsigned int b : bin) {
for(int i=0; i<32; ++i) {
if(p == endp) break;
*(p++) = b & (1 << i) ? 255 : 0;
} } }
Qrc(const Qrc&) = delete;
Qrc& operator = (const Qrc&) = delete;
~Qrc() { delete [] data; }
};
struct BIH { // a private definition of BITMAPINFOHEADER
unsigned int sz;
int width, height;
unsigned short planes;
short bits;
unsigned int compress, szimage;
int xppm, yppm;
unsigned int clrused, clrimp;
};
void SaveBmp(const char* filename, const Qrc& qrc) {
// Asker's Qrc struct delivered as a pointer, from a C API, but this example doesn't mimic that.
std::ofstream ofs(filename, std::ios_base::out | std::ios_base::binary);
if(!ofs) {
std::cout << "Writing " << filename << " failed\n";
return;
}
const int side_len = qrc.dimsize; // width and height of the (square) QR Code
const int pixel_side_len = 4; // QRC element's size in the bmp image (in pixels)
const int bmp_line_bytes = side_len * pixel_side_len * 3;
const int bmp_line_pad_bytes = (4 - bmp_line_bytes % 4) % 4; // bmp line data padding size
const int bmp_data_size = side_len * (bmp_line_bytes + bmp_line_pad_bytes);
BIH bih = { sizeof(bih) };
bih.width = side_len * pixel_side_len; // element count * element size
bih.height = -side_len * pixel_side_len; // negative height => data begins at top of image
bih.planes = 1;
bih.bits = 24;
const int header_size = sizeof(bih) + 14; // size of the bmp file header
const int filesize = header_size + bmp_data_size; // size of the whole file
ofs.write("BM", 2);
ofs.write(reinterpret_cast<const char*>(&filesize), 4);
ofs.write("\0\0\0\0", 4); // 2x 16-bit reserved fields
ofs.write(reinterpret_cast<const char*>(&header_size), 4);
ofs.write(reinterpret_cast<const char*>(&bih), sizeof(bih));
// pixel colors, as Blue, Green, Red char-valued triples
// the terminating null also makes these usable as 32bpp BGRA values, with Alpha always 0.
static const char fg_color[] = "\0\0\0";
static const char bg_color[] = "\xff\xff\xff";
auto pd = qrc.data;
// send pixel data directly to the bmp file
// QRC elements are expanded into squares
// whose sides are "pixel_side_len" in length.
for(int y=0; y<side_len; ++y) {
for(int j=0; j<pixel_side_len; ++j) {
auto pdj = pd;
for(int x=0; x<side_len; ++x) {
for(int i=0; i<pixel_side_len; ++i) {
// *pdj will be 0 or 255 (from "fake" Qrc)
// Using "*pdj & 1" here, just to match asker's code
// without knowing why this was done.
ofs.write(*pdj & 1 ? fg_color : bg_color, 3);
}
++pdj;
}
if(bmp_line_pad_bytes) {
ofs.write("\0\0\0", bmp_line_pad_bytes);
}
}
pd += side_len;
}
}
int main() {
SaveBmp("MyQrCode.bmp", Qrc());
}

CImg: Image binarization result fails

So, the problem in my following code is that the result of the image binarization becomes too dark. (There was even an example image I have whose binary image becomes wholly black.)
I have been searching any mistake in my code for a very long time, and have found none that seemingly looks problematic to me.
Below is the image I want to binarize:
Image before binarized - in my code is named: "hildebrantmed.bmp"
Below is the resulting binary image:
Image after binarized
Before I show you my source code, here are the 'rules' in the image binarization (since this is an assignment I recently got):
I am not allowed to use any other libraries than CImg.
The programming language to use is C/C++. Not any other else.
Normally, the Otsu's method is the choice. However, I may be allowed to use other algorithms if it is better.
Lastly, here is my source code:
#include <iostream>
#include <CImg.h>
using namespace std;
using namespace cimg_library;
/**
* Generate histogram of the grayscale image
*/
int * generate_histogram(CImg<unsigned char> img)
{
int histogram[256];
// initialize default values for histogram
for (int i = 0; i < 256; i++)
{
histogram[i] = 0;
}
// increment intensity for histogram
for (int i = 0; i < img.height(); i++)
{
for (int j = 0; j < img.width(); j++)
{
int gray_value = img(j, i, 0, 0);
histogram[gray_value]++;
}
}
return histogram;
}
/**
* Find threshold value from the grayscale image's histogram
*/
int otsu_threshold(CImg<unsigned char> img)
{
int * histogram = generate_histogram(img); // image histogram
int total = img.width() * img.height(); // total pixels
double sum = 0;
int i;
for (i = 0; i < 256; i++)
{
sum += i * histogram[i];
}
double sumB = 0;
int wB = 0;
int wF = 0;
double var_max = 0;
int threshold = 0;
for (i = 0; i < 256; i++)
{
wB += histogram[i];
if (wB == 0) continue;
wF = total - wB;
if (wF == 0) continue;
sumB += (double)(i * histogram[i]);
double mB = sumB / wB;
double mF = (sum - sumB) / wF;
double var_between = (double)wB * (double)wF * (mB - mF) * (mB - mF);
if (var_between > var_max)
{
var_max = var_between;
threshold = i;
}
}
return threshold;
}
/**
* Main function
*/
int main(int argc, char * argv[])
{
// retrieve image from its path
CImg<unsigned char> img("hildebrantmed.bmp");
const int width = img.width();
const int height = img.height();
// initialize a new image for img's grayscale
CImg<unsigned char> gray_img(width, height, 1, 1, 0);
// from RGB divided into three separate channels
CImg<unsigned char> imgR(width, height, 1, 3, 0);
CImg<unsigned char> imgG(width, height, 1, 3, 0);
CImg<unsigned char> imgB(width, height, 1, 3, 0);
// for all (x, y) pixels in image
cimg_forXY(img, x, y)
{
imgR(x, y, 0, 0) = img(x, y, 0, 0),
imgG(x, y, 0, 1) = img(x, y, 0, 1),
imgB(x, y, 0, 2) = img(x, y, 0, 2);
// separate the channels
int R = (int)img(x, y, 0, 0);
int G = (int)img(x, y, 0, 1);
int B = (int)img(x, y, 0, 2);
// obtain gray value from different weights of RGB channels
int gray_value = (int)(0.299 * R + 0.587 * G + 0.114 * B);
gray_img(x, y, 0, 0) = gray_value;
}
// find threshold of grayscale image
int threshold = otsu_threshold(gray_img);
// initialize a binary image version of img
CImg<unsigned char> binary_img(width, height, 1, 1, 0);
// for every (x, y) pixel in gray_img
cimg_forXY(img, x, y)
{
int gray_value = gray_img(x, y, 0, 0);
// COMPARE gray_value with threshold
int binary_value;
// gray_value > threshold: 255 (white)
if (gray_value > threshold) binary_value = 255;
// gray_value < threshold: 0 (black)
else binary_value = 0;
// assign binary_value to each of binary_img's pixels
binary_img(x, y, 0, 0) = binary_value;
}
// display the images
CImgDisplay src_disp(img, "Source image");
CImgDisplay gray_disp(gray_img, "Grayscale image");
CImgDisplay binary_disp(binary_img, "Binary image");
while (!src_disp.is_closed() && !gray_disp.is_closed() && !binary_disp.is_closed())
{
src_disp.wait();
gray_disp.wait();
}
return 0;
}
If you find that another algorithm would work better, please provide with the algorithm and source code in your answer. Thanks for your attention.
First error: you're trying to return an array's pointer which actually gets destroyed as soon as the generate_histogram function ends.
To make it work properly, you should supply the pointer to an array from the calling function, something like:
{
//[....]
int histogram[256];
generate_histogram(img, histogram);
//[....]
}
int * generate_histogram(CImg<unsigned char> img, int* arHistogram)
{
//[....]
}

How do take a screenshot correctly with xlib?

I am trying to capture an image of the screen for use in screencasting. Thus I need a fast solution, and cannot rely on shell programs such as import or xwd.
This is the code I have written so far, but it fails and gives me a junk image, which just seems to show fragments of several images with odd colors tossed together.
Any ideas on what I am doing wrong?
#include <X11/Xlib.h>
#include <X11/X.h>
#include <cstdio>
#include <CImg.h>
using namespace cimg_library;
int main()
{
Display *display = XOpenDisplay(NULL);
Window root = DefaultRootWindow(display);
XWindowAttributes gwa;
XGetWindowAttributes(display, root, &gwa);
int width = gwa.width;
int height = gwa.height;
XImage *image = XGetImage(display,root, 0,0 , width,height,AllPlanes, ZPixmap);
unsigned char *array = new unsigned char[width * height * 3];
unsigned long red_mask = image->red_mask;
unsigned long green_mask = image->green_mask;
unsigned long blue_mask = image->blue_mask;
for (int x = 0; x < width; x++)
for (int y = 0; y < height ; y++)
{
unsigned long pixel = XGetPixel(image,x,y);
unsigned char blue = pixel & blue_mask;
unsigned char green = (pixel & green_mask) >> 8;
unsigned char red = (pixel & red_mask) >> 16;
array[(x + width * y) * 3] = red;
array[(x + width * y) * 3+1] = green;
array[(x + width * y) * 3+2] = blue;
}
CImg<unsigned char> pic(array,width,height,1,3);
pic.save_png("blah.png");
printf("%ld %ld %ld\n",red_mask>> 16, green_mask>>8, blue_mask);
return 0;
}
You are mistaken about the way array is laid out in memory, as you can find out by declaring img before the loop and adding this printf to your inner loop:
printf("%ld %ld %u %u %u\n",x,y,pic.offset(x,y,0),pic.offset(x,y,1),pic.offset(x,y,2));
This yields (on my 1920x1200 screen):
0 0 0 2304000 4608000
0 1 1920 2305920 4609920
0 2 3840 2307840 4611840
and so on, indicating that the red/green/blue subimages are kept "together" instead of the three color components of a single pixel being adjacent to each other.
The builtin CImg accessors will make your code work:
pic(x,y,0) = red;
pic(x,y,1) = green;
pic(x,y,2) = blue;
You can use libpng
int code = 0;
FILE *fp;
png_structp png_ptr;
png_infop png_info_ptr;
png_bytep png_row;
// Open file
fp = fopen ("test.png", "wb");
if (fp == NULL){
fprintf (stderr, "Could not open file for writing\n");
code = 1;
}
// Initialize write structure
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL){
fprintf (stderr, "Could not allocate write struct\n");
code = 1;
}
// Initialize info structure
png_info_ptr = png_create_info_struct (png_ptr);
if (png_info_ptr == NULL){
fprintf (stderr, "Could not allocate info struct\n");
code = 1;
}
// Setup Exception handling
if (setjmp (png_jmpbuf (png_ptr))){
fprintf(stderr, "Error during png creation\n");
code = 1;
}
png_init_io (png_ptr, fp);
// Write header (8 bit colour depth)
png_set_IHDR (png_ptr, png_info_ptr, width, height,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
// Set title
char *title = "Screenshot";
if (title != NULL){
png_text title_text;
title_text.compression = PNG_TEXT_COMPRESSION_NONE;
title_text.key = "Title";
title_text.text = title;
png_set_text (png_ptr, png_info_ptr, &title_text, 1);
}
png_write_info (png_ptr, png_info_ptr);
// Allocate memory for one row (3 bytes per pixel - RGB)
png_row = (png_bytep) malloc (3 * width * sizeof (png_byte));
// Write image data
int x, y;
for (y = 0; y < height; y++){
for (x = 0; x < width; x++){
unsigned long pixel = XGetPixel (image, x, y);
unsigned char blue = pixel & blue_mask;
unsigned char green = (pixel & green_mask) >> 8;
unsigned char red = (pixel & red_mask) >> 16;
png_byte *ptr = &(png_row[x*3]);
ptr[0] = red;
ptr[1] = green;
ptr[2] = blue;
}
png_write_row (png_ptr, png_row);
}
// End write
png_write_end (png_ptr, NULL);
// Free
fclose (fp);
if (png_info_ptr != NULL) png_free_data (png_ptr, png_info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL) png_destroy_write_struct (&png_ptr, (png_infopp)NULL);
if (png_row != NULL) free (png_row);
image has to stored in memory as R1R2R3R4R5R6......G1G2G3G4G5G6.......B1B2B3B4B5B6.
cimg storage