I want to take screenshot from OpenGL window and save it to image file of any type. DevIL method described here gives correct PNG. Replace ilSaveImage with ilSave and you can save image in different formats. SOIL method here gives flipped vertically image. Replacing code below
vector< unsigned char > buf( w * h * 3 );
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, &buf[0] );
int err = SOIL_save_image ("img.bmp", SOIL_SAVE_TYPE_BMP, w, h, 3, &buf[0]);
with only one line creates the correct image.
int err = SOIL_save_screenshot("img.bmp",SOIL_SAVE_TYPE_BMP, 0, 0, w, h);
Q1: Is any more convenient alternatives using other libraries?
Q2: Which one is the best way? Comparison is appreciated e.g. performance\compatibility.
Related
I am trying to save texture to a image using SOIL2 library.
int _width, _height;
unsigned char* _image = SOIL_load_image("C:\\Temp\\RED.png", &_width, &_height, 0, SOIL_LOAD_RGB);
int save_result = SOIL_save_image
(
"C:\\temp\\atlas.png",
SOIL_SAVE_TYPE_PNG,
_width, _height, GL_RGB,
_image
);
But image does not gets saved the return values from the saved function is 0.
In my case (GL_RGBA) I needed to set the channels parameter to 4 (not GL_RGBA) to get it working. In your case, you probably need to change it to 3 (not GL_RGB).
I need to create a QImage or something that can be drawn onto a screen from a geotiff image. Unfortunately QT's built-in TIFF support chokes on the geotiff structures ... so to achieve this I have used the following code (which is more or less a copy paste from the gdal "tutorial" page (https://gdal.org/gdal_tutorial.html) except the image creation part ):
GDALRasterBand *poBand;
int nBlockXSize, nBlockYSize;
int bGotMin, bGotMax;
double adfMinMax[2];
poBand = poDataset->GetRasterBand( 1 );
poBand->GetBlockSize( &nBlockXSize, &nBlockYSize );
adfMinMax[0] = poBand->GetMinimum( &bGotMin );
adfMinMax[1] = poBand->GetMaximum( &bGotMax );
if( ! (bGotMin && bGotMax) )
GDALComputeRasterMinMax((GDALRasterBandH)poBand, TRUE, adfMinMax);
float *pafScanline;
int nXSize = poBand->GetXSize();
int nYSize = poBand->GetYSize();
pafScanline = (float *) CPLMalloc(sizeof(float)*nXSize * nYSize);
poBand->RasterIO( GF_Read, 0, 0, nXSize, nYSize,
pafScanline, nXSize, nYSize, GDT_Float32, 0, 0 );
QImage* image = new QImage((unsigned char*)pafScanline,
nXSize, nYSize,
QImage::Format_RGB32);
image->save("blaa.jpg");
Now, the image I try to load is on the left side and the one that gets displayed (and saved by Qt) is on the right side.
Question: how to create a properly coloured image from the tiff data given that I get in floats, and I have no idea how to create a QImage data from a bunch of floats.
Your input GeoTIFF may not have a single floating point band, but rather 3 (or 4) 8 bit bands.
Bands in GeoTIFF are basically the image channels. Unlike other image formats, these channels can also have floating point values.
You can have a look at the GDAL documentation here to know more about the allowed formats.
So it is likely (although I can't be 100% sure without looking at it) that your file is just an RGBA GeoTIFF, and hence, has 4 UNIT8 bands.
Therefore, your call to RasterIO is completely wrong. You should iterate over the 4 bands and copy the with RasterIO to the QImage memory buffer, respecting the bands order.
Something like:
int nBands = poDataset->GetRasterCount();
for(int b=0; b < nBands; b++)
{
GDALRasterBand *band = poDataset->GetRasterBand(b);
if(band != nullptr)
{
CPLErr error = band->RasterIO(GF_Write, 0, 0, image.width(), image.height(), image.bits() + b, image.width(), image.height(), GDT_Byte, nBands, 0);
if(error != CE_None)
{
// REPORT ERROR
}
}
}
Please note that the code above is missing all the required checks (ensuring band type is Byte, etc), and depending on your file the band order may vary (BGRA, RGBA, ecc).
I have a piece of GLvoid* data that contains an entire image, which is periodically updated throughout my program. I use glTexImage2D to initialize a texture on the GPU with this data.
I would like to use glTexSubImage2D to update parts of the texture as necessary. The documentation for glTexSubImage2D describes the GLvoid* pixels argument as such:
Specifies a pointer to the image data in memory.
What "image data" is this expecting? Can I provide the entire GLvoid* data, or is it expecting a buffer that only contains the data being copied?
If it expects the partial data, is there an alternative way to provide the whole buffer instead?
You can provide entire image data with call
glTexSubImage2D( target, level, 0, 0, W, H, format, type, pixels);
W and H are texture width and height, the whole texture will be copied, pixels is W*H array. Or you can provide only modified data with call
glTexSubImage2D( target, level, offset_x, offset_y, w, h, format, type, pixels);
where w and h is weigth and height of modified data so offset_x + w < W and offset_y + y < Y. pixels is w*h array.
Edit:
glPixelStorei( GL_UNPACK_ROW_LENGTH, W);
glPixelStorei( GL_UNPACK_SKIP_PIXELS, offset_x);
glPixelStorei( GL_UNPACK_SKIP_ROWS, offset_y);
glTexSubImage2D( target, level, offset_x, offset_y, w, h, format, type, pixels);
where pixels is W*H
It's only the data to be copied. Or you will have to waste a lot of memory to upload only a part of the image(sometimes the only part you currently have).
P.S. And it's rather painful that it doesn't even support strided data. So if you have a full image, you can't upload left or right half of it without copying it to a smaller buffer.
I am trying to create a gl::Texture object using image data as a BYTE * with the parameters below.
FreeGLUT - I use this to create a 2d texture and bind it to a quad.:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureWidth, textureHeight, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0); etc etc
This works fine, however I cannot find a way to create a gl::Texture object in Cinder.
text = gl::Texture(loadImage(loadAsset("text.jpg"))); // works with images files
text = gl::Texture(data, GL_RGBA8, 640, 480); // BTYE * data This gives me a grey screen -
This seemed the most likely, however I have no idea what to do with Format format = format();
Texture (const unsigned char *data, int dataFormat, int aWidth, int aHeight, Format format=Format())
I really have no understanding of how this works and cant find any better tutorials online. Thanks.
apparently dataFormat is the format parameter to glTexImage2D and should be something like GL_RGBA or GL_RGB (or GL_BGRA_EXT as in the example you provided)
What you want is the "internal format" which is set via the Format struct. So you set it like:
gl::Texture::Format fmt;
fmt.setInternalFormat(GL_RGBA8);
text = gl::Texture(data, GL_RGBA, 640, 480, fmt);
The GL_UNSIGNED_BYTE format is pre-set in that constructor of the cinder wrapper
Sometimes it can be easier to just look at the code instead of trying to find the 100% applicable documentation
I want to get every OpenGL frame from an animation with glReadPixels() and convert the data to OpenCV::Mat. I know that glReadPixels() gets the data by rows from the lower one to upper one, from left to right. On the other hand, OpenCV stores the data differently.
Does anybody know any library or any tutorial/example that helps me to convert data from glReadPixels to a OpenCV:Mat in C++?
SUMMARY
OpenGL frame -----------------------> CV::Mat
Data from left to right, Data from left to right,
bottom to top. top to bottom.
First we create an empty (or unititialized) cv::Mat for our data to be read into directly. This can be done once at startup, but on the other hand cv::Mat::create doesn't really cost much when the image already has matching size and type. The type depends on your needs, usually it's something like CV_8UC3 for a 24-bit color image.
cv::Mat img(height, width, CV_8UC3);
or
img.create(height, width, CV_8UC3);
Then you have to account for cv::Mat not neccessarily storing image rows contiguously. There might be a small padding value at the end of each row to make rows 4-byte aligned (or 8?). So you need to mess with the pixel storage modes:
//use fast 4-byte alignment (default anyway) if possible
glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
//set length of one complete row in destination data (doesn't need to equal img.cols)
glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
Next, the type of the matrix influences the format and type parameters of glReadPixels. If you want color images you have to keep in mind that OpenCV usually stores color values in BGR order, so you need to use GL_BGR(A) (which were added with OpenGL 1.2) instead of GL_RGB(A). For one component images use either GL_LUMINANCE (which sums the individual color components) or GL_RED, GL_GREEN, ... (to get an individual component). So for our CV_8UC3 image the final call to read it directly into the cv::Mat would be:
glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);
Finally, OpenCV stores images from top to bottom. So you may need to either flip them after getting them or render them flipped in OpenGL in the first place (this can be done by adjusting the projection matrix, but keep an eye on triangle orientation in this case). To flip a cv::Mat vertically, you can use cv::flip:
cv::flip(img, flipped, 0);
So to keep in mind OpenCV:
stores images from top to bottom, left to right
stores color images in BGR order
might not store image rows tightly packed
unsigned char* getPixelData( int x1, int y1, int x2, int y2 )
{
int y_low, y_hi;
int x_low, x_hi;
if ( y1 < y2 )
{
y_low = y1;
y_hi = y2;
}
else
{
y_low = y2;
y_hi = y1;
}
if ( x1 < x2 )
{
x_low = x1;
x_hi = x2;
}
else
{
x_low = x2;
x_hi = x1;
}
while ( glGetError() != GL_NO_ERROR )
{
;
}
glReadBuffer( GL_BACK_LEFT );
glDisable( GL_TEXTURE_2D );
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
unsigned char *data = new unsigned char[ ( x_hi - x_low + 1 ) * ( y_hi - y_low + 1 ) * 3 ];
glReadPixels( x_low, y_low, x_hi-x_low+1, y_hi-y_low+1, GL_RGB, GL_UNSIGNED_BYTE, data );
if ( glGetError() != GL_NO_ERROR )
{
delete[] data;
return 0;
}
else
{
return data;
}
}
use:
CvSize size = cvSize( 320, 240 );
unsigned char *pixel_buf = getPixelData( 0, 0, size.width - 1, size.height - 1 );
if ( pixel_buf == 0 )
return 0;
IplImage *result = cvCreateImage( size, IPL_DEPTH_8U, 3 );
memcpy( result->imageData, pixel_buf, size.width * size.height * 3 );
delete[] pixel_buf;