glReadPixels save to BMP - Color Dirstortion - opengl

I'm using OpenGL and PCL_lib right now, And I want to draw a accessory on the background pic.
Here is my output_image on display:
Image source is PCD format(PCL lib), drew by OpenGL
and Here is my code where to save the GL_pic:
char filename[100];
sprintf(filename, "path", _mframe_Count);
// Set Parameter
GLint x, y, width, height;
int i;
unsigned long imageSize;
GLenum lastBuffer
glGetIntegerv( GL_VIEWPORT, mViewPort );
x = mViewPort[0];
y = mViewPort[1];
width = mViewPort[2];
height = mViewPort[3];
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
bmfh.bfType = 0x4D42;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(bmfh) + sizeof(bmih);
imageSize = ((width+((4-(width%4))%4))*height*3)+2;
bmfh.bfSize = imageSize + sizeof(bmfh) + sizeof(bmih);
GLubyte *pImageData = (GLubyte *)malloc(imageSize);
// Allocate apicture buffer
if(pImageData == NULL)
{
printf("[Error] : grab_GL(), Cannot create Memory_Sapce for pImageData.\n");
return false;
}
// Byte alignment (that is, no alignment)
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH,0);
glPixelStorei(GL_PACK_SKIP_ROWS,0);
glPixelStorei(GL_PACK_SKIP_PIXELS,0);
glPixelStorei(GL_PACK_SWAP_BYTES,1);
glGetIntegerv(GL_READ_BUFFER, ( GLint *)&lastBuffer);
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, pImageData);
pImageData[imageSize-1] = 0;
pImageData[imageSize-2] = 0;
glReadBuffer(lastBuffer);
FILE *pWritingFile = fopen(filename, "wb");
if (pWritingFile == NULL)
{
free(pImageData);
printf("[Error] : grab_GL(), Cannot open %s for writting.\n", filename);
return false;
}
// BMP Structures
bmih.biSize = sizeof(bmih);
bmih.biWidth = width;
bmih.biHeight = height;
bmih.biPlanes = 1;
bmih.biBitCount = 24;
bmih.biCompression = 0;
bmih.biSizeImage = imageSize;
bmih.biXPelsPerMeter = 2835;
bmih.biYPelsPerMeter = 2835;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
// BMP Compress
fwrite(&bmfh, 8, 1, pWritingFile);
fwrite(&bmfh.bfReserved2, sizeof(bmfh.bfReserved2), 1, pWritingFile);
fwrite(&bmfh.bfOffBits, sizeof(bmfh.bfOffBits), 1, pWritingFile);
fwrite(&bmih, sizeof(bmih), 1, pWritingFile);
fwrite(pImageData, imageSize, 1, pWritingFile);
// End The Process
fclose(pWritingFile);
free(pImageData);
return true;
But when save the image to BMP file which shown on the display, it becomes:

It looks like the writing to disk is a little difficult to interpret:
// BMP Compress
fwrite(&bmfh, 8, 1, pWritingFile);
fwrite(&bmfh.bfReserved2, sizeof(bmfh.bfReserved2), 1, pWritingFile);
fwrite(&bmfh.bfOffBits, sizeof(bmfh.bfOffBits), 1, pWritingFile);
fwrite(&bmih, sizeof(bmih), 1, pWritingFile);
fwrite(pImageData, imageSize, 1, pWritingFile);
I don't see why you are writing the elements of bmfh separately so just make sure everything is clear by doing this instead:
// BMP Compress
fwrite(&bmfh, sizeof(bmfh), 1, pWritingFile);
fwrite(&bmih, sizeof(bmih), 1, pWritingFile);
fwrite(pImageData, imageSize, 1, pWritingFile);
If that makes no difference, then the appearance of your image is that the colour components are reversed (RGB rather than BGR) - although glPixelStorei(GL_PACK_SWAP_BYTES,1) is not supposed to affect the pixel data itself, it would be worth trying either this:
glPixelStorei(GL_PACK_SWAP_BYTES,1);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pImageData);
or this:
glPixelStorei(GL_PACK_SWAP_BYTES,0);
glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, pImageData);

Related

C++ Gdi+ convert image to grayscale

Trying to convert 32,24,16,8 bit images to their grayscale presentation. I read about using BitBlt, but maybe exist some light way built-in opportunity
in GDI+?
Code:
#include <vector>
...
class gdiplus_init
{
ULONG_PTR token;
public:
gdiplus_init()
{
Gdiplus::GdiplusStartupInput tmp;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
}
~gdiplus_init()
{
Gdiplus::GdiplusShutdown(token);
}
};
bool getbits(const wchar_t *filename, Gdiplus::PixelFormat pixelformat,
std::vector<BYTE> &bitmapinfo, std::vector<BYTE> &bits, int &w, int &h)
{
gdiplus_init init;
WORD bpp = 0;
int usage = DIB_RGB_COLORS;
int palettesize = 0;
switch(pixelformat)
{
case PixelFormat8bppIndexed:
bpp = 8;
usage = DIB_PAL_COLORS;
palettesize = 256 * sizeof(RGBQUAD);
break;
case PixelFormat16bppRGB555: bpp = 16; break;
case PixelFormat16bppRGB565: bpp = 16; break;
case PixelFormat24bppRGB: bpp = 24; break;
case PixelFormat32bppRGB: bpp = 32; break;
default:return false;
}
auto src = Gdiplus::Bitmap::FromFile(filename);
if(src->GetLastStatus() != Gdiplus::Status::Ok)
return false;
auto dst = src->Clone(0, 0, src->GetWidth(), src->GetHeight(),
pixelformat);
w = src->GetWidth();
h = src->GetHeight();
HBITMAP hbitmap;
Gdiplus::Color color;
dst->GetHBITMAP(color, &hbitmap);
//allocate enough memory for bitmapinfo and initialize to zero
//it's sizeof BITMAPINFO structure + size of palette
bitmapinfo.resize(sizeof(BITMAPINFO) + palettesize, 0);
//fill the first 6 parameters
BITMAPINFO* ptr = (BITMAPINFO*)bitmapinfo.data();
ptr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); //don't skip
ptr->bmiHeader.biWidth = w;
ptr->bmiHeader.biHeight = h;
ptr->bmiHeader.biPlanes = 1;
ptr->bmiHeader.biBitCount = bpp;
ptr->bmiHeader.biCompression = BI_RGB;
//magic formula to calculate the size:
//this is roughly w * h * bytes_per_pixel, it's written this way
//to account for "bitmap padding"
DWORD size = ((w * bpp + 31) / 32) * 4 * h;
//allocate memory for image
bits.resize(size, 0);
//finally call GetDIBits to fill bits and bitmapinfo
HDC hdc = GetDC(0);
GetDIBits(hdc, hbitmap, 0, h, &bits[0], (BITMAPINFO*)&bitmapinfo[0], usage);
ReleaseDC(0, hdc);
//cleanup
delete src;
delete dst;
return true;
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
...
std::vector<BYTE> bi; //automatic storage
std::vector<BYTE> bits;
int w, h;
//24-bit test
if(getbits(L"c:\\test\\24bit.bmp", PixelFormat24bppRGB, bi, bits, w, h))
StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h,
bits.data(), (BITMAPINFO*)bi.data(), DIB_RGB_COLORS, SRCCOPY);
//8-bit test
if(getbits(L"c:\\test\\8bit.bmp", PixelFormat8bppIndexed, bi, bits, w, h))
StretchDIBits(dc, 0, 220, w, h, 0, 0, w, h,
bits.data(), (BITMAPINFO*)bi.data(), DIB_PAL_COLORS, SRCCOPY);
}
You can draw the GDI+ directly with various transformation. Use Gdiplus::Graphics to draw on device context.
For grayscale conversion, RGB values all have to be the same. Gdiplus::ColorMatrix can transform the colors. Green is usually more important, it gets more weight.
void draw(CDC *pdc)
{
//this line should be in OnCreate or somewhere other than paint routine
Gdiplus::Bitmap source(L"file.jpg");
//gray scale conversion:
Gdiplus::ColorMatrix matrix =
{
.3f, .3f, .3f, 0, 0,
.6f, .6f, .6f, 0, 0,
.1f, .1f, .1f, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attr;
attr.SetColorMatrix(&matrix,
Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
Gdiplus::Graphics gr(pdc->GetSafeHdc());
Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
Gdiplus::RectF rect(0, 0, w, h);
gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);
}
Note, I used rough values for grayscale matrix. See the answer mentioned in comment for a better matrix.
To convert the file, the process is similar, except use Gdiplus::Graphics to create memory dc and save it.
int GetEncoderClsid(const WCHAR* format, CLSID* clsid)
{
int result = -1;
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
Gdiplus::GetImageEncodersSize(&num, &size);
if(size)
{
Gdiplus::ImageCodecInfo* codec = (Gdiplus::ImageCodecInfo*)(malloc(size));
GetImageEncoders(num, size, codec);
for(UINT j = 0; j < num; ++j)
if(wcscmp(codec[j].MimeType, format) == 0)
{
*clsid = codec[j].Clsid;
result = j;
}
free(codec);
}
return result;
}
bool convert_grayscale(const wchar_t *file_in, const wchar_t *file_out)
{
CStringW extension = PathFindExtensionW(file_out);
extension.Remove(L'.');
extension.MakeLower();
if(extension == L"jpg") extension = L"jpeg";
extension = L"image/" + extension;
CLSID clsid;
if(GetEncoderClsid(extension, &clsid) == -1)
return false;
Gdiplus::Bitmap source(file_in);
if(source.GetLastStatus() != Gdiplus::Status::Ok)
return false;
Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
Gdiplus::RectF rect(0, 0, w, h);
Gdiplus::Bitmap copy((INT)w, (INT)h, source.GetPixelFormat());
Gdiplus::ColorMatrix matrix =
{
.3f, .3f, .3f, 0, 0,
.6f, .6f, .6f, 0, 0,
.1f, .1f, .1f, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attr;
attr.SetColorMatrix(&matrix,
Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
Gdiplus::Graphics gr(&copy);
gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);
auto st = copy.Save(file_out, &clsid);
return st == Gdiplus::Status::Ok;
}
...
convert_grayscale(L"source.jpg", L"destination.jpg");

stack error while trying to modify hbitmap data

I'm trying to modify a hbitmap to add transparent pixels before rendering it (but that's not the question) and after some googling I can't mak my code to work.
This is what I'm trying:
HBITMAP hBitmap = NULL, hBitmapOld = NULL;
HDC hMemDC = NULL;
BLENDFUNCTION bf;
hMemDC = CreateCompatibleDC(hdc);
hBitmapOld = (HBITMAP)SelectObject(hMemDC, bitmap);
BITMAPINFO MyBMInfo = { 0 };
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
if (0 == GetDIBits(hMemDC, bitmap, 0, height, NULL, &MyBMInfo, DIB_RGB_COLORS))
return;
// create the pixel buffer
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
if (0 == GetDIBits(hMemDC, bitmap, 0, height, lpPixels, &MyBMInfo, DIB_RGB_COLORS))
return;
for (int i = 0; i < width*height; i++)//i know there's 4 bytes per pixel, it's just to try
lpPixels[i] = 0;
if (0 == SetDIBits(hMemDC, bitmap, 0, height, lpPixels, &MyBMInfo, DIB_RGB_COLORS))
return;
delete[] lpPixels;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255; //transparency value between 0-255
bf.AlphaFormat = 0;
AlphaBlend(hdc, xabs, yabs, width, height, hMemDC, 0, 0, width, height, bf);
SelectObject(hMemDC, hBitmapOld);
DeleteDC(hMemDC);
DeleteObject(hBitmap);
Actualy I'm just trying to set the pixel to 0 which should set black (eventualy transparent) pixels for a quarter of the image (as I'm just going from 0 to w*x and pixels are 4 bytes long).
But there's some data corruption so that when that function exits I get a exception. Then my code is not correct.
I can say the bitmap is well loaded, and I get the good bitmap info from GetDIBits.
Thanks
From MSDN:
biSizeImage
The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps.
If biCompression is BI_JPEG or BI_PNG, biSizeImage indicates the size of the JPEG or PNG image buffer, respectively.
So I suspect that you create effectively empty array since bitmaps are typically not compressed and suffer from buffer overrun later.
To obtain require buffer size you should check biCompression and then (assuming that it is uncompressed BI_RGB) multiply biWidth * biHeight * biBitCount / 8
I folowwed the advice of VTT but it still didn't work, it seems that the problem came from the BITMAPINFO which was not used properly, so I followed the way microsoft does here and it works:
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
if (0 == GetDIBits(hMemDC, bitmap, 0, height, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS))
return;
// create the pixel buffer
BYTE* lpPixels = new BYTE[bi.biWidth * bi.biHeight * bi.biBitCount / 8];
if (0 == GetDIBits(hMemDC, bitmap, 0, height, lpPixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS))
return;
for (int i = 0; i < width*height; i++)//i know there's 4 bytes per pixel, it's just to try
lpPixels[i] = 0;
if (0 == SetDIBits(hMemDC, bitmap, 0, height, lpPixels, (BITMAPINFO*)&bi, DIB_RGB_COLORS))
return;

GDIPlus DrawImage white color is getting black

I'm using GDIPlus to scale image from a buffer.
I'm taking a screenshot of my desktop.
Here is my code:
BYTE *Duplication::scaleBuffer(unsigned char *data, int width, int height)
{
Gdiplus::Bitmap bitmap(width, height, 4 * width, PixelFormat32bppARGB, data);
INT n_width = mWidthResolution;
INT n_height = mHeightResolution;
double ratio = ((double)width) / ((double)height);
if (width > height) {
n_height = (double)n_width / ratio;
}
else {
n_width = n_height * ratio;
}
Gdiplus::Bitmap newBitmap(n_width, n_height, bitmap.GetPixelFormat());
Gdiplus::Graphics graphics(&newBitmap);
graphics.SetInterpolationMode(Gdiplus::InterpolationModeBilinear);
graphics.DrawImage(&bitmap, 0, 0, n_width, n_height);
Gdiplus::Rect rect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight());
Gdiplus::BitmapData bitmapData;
BYTE *buffer = nullptr;
if (Gdiplus::Ok == newBitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData)) {
int len = bitmapData.Height * std::abs(bitmapData.Stride);
buffer = new BYTE[len];
RtlZeroMemory(buffer, len);
memcpy(buffer, bitmapData.Scan0, len);
newBitmap.UnlockBits(&bitmapData);
}
return buffer;
}
On windows with white background, I got a black background and color is corrupted.. It happens only on some program like "File Browser".. I don't understand why ...

Full screenshot to BMP. Issue with bliting and saving

I want to take a capture of part of screen and save it into BMP. To save picture I plan with SOIL. Bit bliting functions I get here.
Code:
bool saveScreen(string path)
{
string name;
SYSTEMTIME sm;
GetSystemTime(&sm);
name = to_string(sm.wHour) + to_string(sm.wMinute) + to_string(sm.wSecond) + to_string(sm.wMilliseconds)
+ "_" + to_string(sm.wDay) + to_string(sm.wMonth) + to_string(sm.wYear);
path = /*path + "/" +*/ name + ".bmp";
const char *charPath = path.c_str();
BITMAPINFO bmi;
auto& hdr = bmi.bmiHeader;
hdr.biSize = sizeof(bmi.bmiHeader);
hdr.biWidth = screenWidth;
hdr.biHeight = screenHeight;
hdr.biPlanes = 1;
hdr.biBitCount = 32;
hdr.biCompression = BI_RGB;
hdr.biSizeImage = 0;
hdr.biXPelsPerMeter = 0;
hdr.biYPelsPerMeter = 0;
hdr.biClrUsed = 0;
hdr.biClrImportant = 0;
unsigned char* bitmapBits;
HDC hdc = GetDC(NULL);
HDC hBmpDc = CreateCompatibleDC(hdc);
BITMAP bm;
HBITMAP hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)&bitmapBits, nullptr, 0);
SelectObject(hBmpDc, hBmp);
BitBlt(hBmpDc, 0, 0, screenWidth, 1024, hdc, 0, 0, SRCCOPY);
vector< unsigned char > buf(screenWidth* screenHeight* 3);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, bitmapBits);
int texture = SOIL_save_image(charPath, SOIL_SAVE_TYPE_BMP, screenWidth, screenHeight, 3, bitmapBits);
return texture;
}
On output I get this:
Broken BMP
It looks as RGBA/RGB issue, but I don't set RGBA nowhere.
What I missed in the code? It's the right way to get screenshot?
You create 32 bpp image, however pass 3 to SOIL_save_image indicating that it is 24 bpp image.

SDL OpenGL screenshot is black

I create a screenshot of my opengl window with help of SDL library, but it was all black and i dont understand why. How to fix it?
Code:
SDL_Surface * image = SDL_CreateRGBSurface(SDL_SWSURFACE, current_w, current_h, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, current_w, current_h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels);
SDL_SaveBMP(image, "pic.bmp");
SDL_FreeSurface(image);
I've seen that you've found the removing of glReadBuffer call, and for vertical flip, you can take the function here from http://lists.libsdl.org/pipermail/sdl-libsdl.org/2005-January/047965.html :
SDL_Surface* flipVert(SDL_Surface* sfc)
{
SDL_Surface* result = SDL_CreateRGBSurface(sfc.flags, sfc.w, sfc.h,
sfc.format.BytesPerPixel * 8, sfc.format.Rmask, sfc.format.Gmask,
sfc.format.Bmask, sfc.format.Amask);
ubyte* pixels = cast(ubyte*) sfc.pixels;
ubyte* rpixels = cast(ubyte*) result.pixels;
uint pitch = sfc.pitch;
uint pxlength = pitch*sfc.h;
assert(result != null);
for(uint line = 0; line < sfc.h; ++line) {
uint pos = line * pitch;
rpixels[pos..pos+pitch] =
pixels[(pxlength-pos)-pitch..pxlength-pos];
}
return result;
}