I am attempting to program a game engine using SDL and glew with picoPNG as an image loader. I was attempting to make a system to set the icon for the window in my Window class and something strange happened. It appeared the icon worked for some images and it didn't for others. I barely know anything about how SDL_Surface works so I used some websites to find some information. (I can't post links to them because I only have 8 out of 10 required reputation)
My code:
void Window::setWindowIcon(const std::string& filePath) {
//read file
std::vector<unsigned char> in;
std::vector<unsigned char> out;
unsigned long width, height;
//Use my file loading class to read the image file
if (DPE::IOManager::readFileToBuffer(filePath, in) == false) {
fatalError("Failed to open " + filePath);
}
int errorCode = DPE::decodePNG(out, width, height, &(in[0]), in.size());
if (errorCode != 0) {
fatalError("Failed to decode png file!");
}
Uint32 rmask = 0x000000ff;
Uint32 gmask = 0x0000ff00;
Uint32 bmask = 0x00ff0000;
Uint32 amask = 0xff000000;
_sdlSurface = SDL_CreateRGBSurfaceFrom((void*)&out[0], width, height, 32, width * 4, rmask, gmask, bmask, amask);
if (_sdlSurface == NULL) {
std::cout << SDL_GetError() << std::endl;
fatalError("Failed to create surface!");
}
SDL_SetWindowIcon(_sdlWindow, _sdlSurface);
SDL_FreeSurface(_sdlSurface);
}
Finally, here are the two png files
This one Worked.
This one didn't.
The iteration through the code showed everything was fine and the only notification of an error was that the icon wasn't changing.
Edit: I have changed the color masks to be cross-Endian compatible
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
int shift = 0;
rmask = 0xff000000 >> shift;
gmask = 0x00ff0000 >> shift;
bmask = 0x0000ff00 >> shift;
amask = 0x000000ff >> shift;
#else // little endian, like x86
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
I think I found the answer. It appears that when I was using the alpha pixel, it took up 8 more bpp so I decreased the file size to 75x75 and the image worked.
Related
I'm currently using a modified version of the following code I found here to try and convert a .png resource in my project to a HBITMAP and then into a cv::Map.
cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName) {
cv::Mat src;
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
unsigned int size = SizeofResource(hModule, found);
HGLOBAL loaded = LoadResource(hModule, found);
void* resource_data = LockResource(loaded);
/* Now we decode the PNG */
vector<unsigned char> raw;
unsigned long width, height;
int err = decodePNG(raw, width, height, (const unsigned char*)resource_data, size);
if (err != 0)
{
cout<<"\nError while decoding png splash: "<< err <<endl;
return src;
}
// copy from the window device context to the bitmap device context
BITMAPV5HEADER bmpheader = { 0 };
bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
bmpheader.bV5Width = width;
bmpheader.bV5Height = height;
bmpheader.bV5Planes = 1;
bmpheader.bV5BitCount = 32;
bmpheader.bV5Compression = BI_BITFIELDS;
bmpheader.bV5SizeImage = width * height * 4;
bmpheader.bV5RedMask = 0x00FF0000;
bmpheader.bV5GreenMask = 0x0000FF00;
bmpheader.bV5BlueMask = 0x000000FF;
bmpheader.bV5AlphaMask = 0xFF000000;
bmpheader.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmpheader.bV5Intent = LCS_GM_BUSINESS;
void* converted = NULL;
HDC screen = GetDC(NULL);
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
cout << "Error Final: " << GetLastError() << endl;
/* Copy the decoded image into the bitmap in the correct order */
for (unsigned int y1 = height - 1, y2 = 0; y2 < height; y1--, y2++)
for (unsigned int x = 0; x < width; x++)
{
*((char*)converted + 0 + 4 * x + 4 * width*y2) = raw[2 + 4 * x + 4 * width*y1]; // Blue
*((char*)converted + 1 + 4 * x + 4 * width*y2) = raw[1 + 4 * x + 4 * width*y1]; // Green
*((char*)converted + 2 + 4 * x + 4 * width*y2) = raw[0 + 4 * x + 4 * width*y1]; // Red
*((char*)converted + 3 + 4 * x + 4 * width*y2) = raw[3 + 4 * x + 4 * width*y1]; // Alpha
}
GetDIBits(screen, result, 0, height, src.data, (BITMAPINFO *)&bmpheader, DIB_RGB_COLORS);
cv::Mat Actual = src.clone();
ReleaseDC(NULL, screen);
/* Done! */
return Actual;
}
my .rc file looks like this:
and the resources.h entry looks like this
When running the code and hitting this line, I end up with a 2012 ( ERROR_TAG_NOT_FOUND ) error
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
Found it by calling GetLastError() before and after this line of code
And this is how I call this function in my int main() :
HINSTANCE BotModuleHandle = GetModuleHandle(NULL);
cout << "Attempting to load a resource: " << endl;
cv::Mat S = Resource2mat(BotModuleHandle, MAKEINTRESOURCE(103));
Thanks in advance.
Also any suggestions for a better approach for converting a PNG resource into a cv::Mat are highly appreciated
If all you really wish to achieve is to load the resource image into a cv::Mat, then you can do it with a much shorter function:
cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName)
{
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
unsigned int size = SizeofResource(hModule, found);
HGLOBAL loaded = LoadResource(hModule, found);
void* resource_data = LockResource(loaded);
return cv::imdecode(cv::_InputArray(static_cast<uchar*>(resource_data), size)
, cv::IMREAD_UNCHANGED);
}
LockResource gives you a pointer to a buffer (array of bytes) containing a PNG encoded image (as if you just read the contents of a PNG file into an array). SizeofResource gives you the size of this array in bytes.
OpenCV provides function cv::imdecode, which can decode PNG (and other formats) images from memory buffers. There's just a small issue -- we need to pass both the pointer as well as the size in just one parameter. To do this, we can explicitly construct a temporary cv::_InputArray.
Whole test program:
#include <windows.h>
#include "resource.h"
#include <opencv2/opencv.hpp>
cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName)
{
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
CV_Assert(found);
unsigned int size = SizeofResource(hModule, found);
CV_Assert(size);
HGLOBAL loaded = LoadResource(hModule, found);
CV_Assert(size);
void* resource_data = LockResource(loaded);
CV_Assert(resource_data);
return cv::imdecode(cv::_InputArray(static_cast<uchar*>(resource_data), size)
, cv::IMREAD_UNCHANGED);
}
int main()
{
HINSTANCE hModule = GetModuleHandle(NULL);
cv::Mat image(Resource2mat(hModule, MAKEINTRESOURCE(IDB_PNG1)));
cv::imshow("Resource image", image);
cv::waitKey();
return 0;
}
Resource header resource.h:
#define IDB_PNG1 101
Resource file pngres.rc:
#include "resource.h"
#include "winres.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
IDB_PNG1 PNG "resource.png"
Running this gives me a window with the image correctly displayed:
I am attempting to load an icon from a third party executable for use in SDL_SetWindowIcon.
Based on some debugging, I believe I am loading the icon correctly, but I don't seem to be populating the SDL_Surface correctly.
Here's what I'm trying currently:
//attempts to load an icon resource from the specified assembly
//uses rcName if provided, or rcId (as an int resource id) if rcName is null
//if successful, convert and set it as SDL's window icon
void LoadIconFrom(std::string assembly, int rcId, const char* rcName) {
//get a module handle for the target assembly
HMODULE hModule = LoadLibrary(assembly.c_str());
if (hModule == NULL) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "hModule is null!");
return;
}
//get a handle for the desired icon
HICON hIcon = NULL;
if (rcName == NULL) {
hIcon = LoadIcon(hModule, MAKEINTRESOURCE(rcId));
}
else {
hIcon = LoadIcon(hModule, rcName);
}
if (hIcon == NULL) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "hIcon is null!");
return;
}
//load some info regarding the selected icon, make sure it has bitmap data
ICONINFO ii;
if (!GetIconInfo(hIcon, &ii)) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "IconInfo is null!");
return;
}
if (!ii.hbmColor) {
ShowError("Icon Error", "Icon does not have bitmap data!");
return;
}
//attempt to determine the size of the icon
int iWidth, iHeight;
BITMAP bm;
if (!GetObject(ii.hbmColor, sizeof(bm), &bm)) {
ShowError("Icon Error", "Could not read bitmap data!");
return;
}
iWidth = bm.bmWidth;
iHeight = bm.bmHeight;
//ShowError("Icon Win!!!",(std::string("Loaded icon of size: ") + std::to_string(iWidth) + "x" + std::to_string(iHeight)).c_str());
icon = SDL_CreateRGBSurface(SDL_SWSURFACE, bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
Uint8 * bits = NULL;
Uint8 * temp = NULL;
bits = new Uint8[bm.bmWidthBytes*bm.bmHeight];
temp = new Uint8[bm.bmWidthBytes*bm.bmHeight];
memcpy(temp, bm.bmBits, bm.bmWidthBytes*bm.bmHeight);
Uint8 *ptemp;
Uint8 *pbits = bits;
for (int j = bm.bmHeight - 1; j >= 0; j--)
{
ptemp = temp + j * bm.bmWidthBytes;
for (int x = 0; x < bm.bmWidthBytes; x++)
{
*pbits = *ptemp;
pbits++;
ptemp++;
}
}
if (SDL_MUSTLOCK(icon)) SDL_LockSurface(icon);
memcpy(icon->pixels, bits, bm.bmWidthBytes*bm.bmHeight);
if (SDL_MUSTLOCK(icon)) SDL_UnlockSurface(icon);
delete[] bits;
delete[] temp;
SDL_SetWindowIcon(mainWindow, icon);
}
It crashes at SDL_SetWindowIcon. The last bit is supposed to flip the image over, which I believe to be required from examples I've found. Removing that part doesn't seem to have any effect.
If I don't modify "bits" at all, and leave it empty, the program doesn't crash but I get a blank icon.
What am I missing here?
Edit: I have also tried CreateRGBSurfaceFrom, which seems to have identical behaviour - either blank on a blank array or crashes if there's any data in it.
Edit 2: "icon" is an SDL_Surface*, declared elsewhere.
Edit 3: Using SDL 2.0.7.
Edit 4: FIXED CODE :
//attempts to load an icon resource from the specified assembly
//uses rcName if provided, or rcId (as an int resource id) if rcName is null
//if successful, convert and set it as SDL's window icon
void LoadIconFrom(std::string assembly, int rcId, const char* rcName) {
//todo: make error throwing here only happen in debug, while
//release should just continue on its merry way, iconless
//get a module handle for the target assembly
HMODULE hModule = LoadLibrary(assembly.c_str());
if (hModule == NULL) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "hModule is null!");
return;
}
//get a handle for the desired icon
HICON hIcon = NULL;
if (rcName == NULL) {
hIcon = LoadIcon(hModule, MAKEINTRESOURCE(rcId));
}
else {
hIcon = LoadIcon(hModule, rcName);
}
if (hIcon == NULL) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "hIcon is null!");
return;
}
//load some info regarding the selected icon, make sure it has bitmap data
ICONINFO ii;
if (!GetIconInfo(hIcon, &ii)) {
ShowError((std::string("Icon Error ") + std::to_string(GetLastError())).c_str(), "IconInfo is null!");
return;
}
if (!ii.hbmColor) {
ShowError("Icon Error", "Icon does not have bitmap data!");
return;
}
BITMAP bm;
if (!GetObject(ii.hbmColor, sizeof(bm), &bm)) {
ShowError("Icon Error", "Bitmap data does not exist!");
return;
}
HBITMAP hbitmap = (HBITMAP)CopyImage(ii.hbmColor, IMAGE_BITMAP, bm.bmWidth, bm.bmHeight, LR_CREATEDIBSECTION);
if (!GetObject(hbitmap, sizeof(BITMAP), &bm)) {
ShowError("Icon Error", "Could not read bitmap data!");
return;
}
// Verify that the data we have obtained is a 32bpp bitmap with color info
if (bm.bmBitsPixel != 32) {
ShowError("Icon Error", "Bitmap data not in a 32bpp format!");
return;
}
if (bm.bmBits == NULL) {
ShowError("Icon Error", "Extracted bitmap data is null!");
return;
}
// Create an SDL surface - note the mask varies by platform endian-ness
int rmask = 0x00FF0000;
int gmask = 0x0000FF00;
int bmask = 0x000000FF;
int amask = 0xFF000000;
icon = SDL_CreateRGBSurface(SDL_SWSURFACE, bm.bmWidth, bm.bmHeight, bm.bmBitsPixel, rmask, gmask, bmask, amask);
if (icon == NULL) {
ShowError("Icon Error", (std::string("SDL surface creation failed: ") + SDL_GetError()).c_str());
return;
}
// Re-orient the bytes to flip the image vertically
Uint8 * bits = NULL;
Uint8 * temp = NULL;
bits = new Uint8[bm.bmWidthBytes*bm.bmHeight];
temp = new Uint8[bm.bmWidthBytes*bm.bmHeight];
memcpy(temp, bm.bmBits, bm.bmWidthBytes*bm.bmHeight);
Uint8 *ptemp;
Uint8 *pbits = bits;
for (int j = bm.bmHeight - 1; j >= 0; j--)
{
ptemp = temp + j * bm.bmWidthBytes;
for (int x = 0; x < bm.bmWidthBytes; x++)
{
*pbits = *ptemp;
pbits++;
ptemp++;
}
}
// Copy the formatted bits to the surface
if (SDL_MUSTLOCK(icon)) SDL_LockSurface(icon);
memcpy(icon->pixels, bits, bm.bmWidthBytes*bm.bmHeight);
if (SDL_MUSTLOCK(icon)) SDL_UnlockSurface(icon);
// Set the window icon to the loaded surface
SDL_SetWindowIcon(mainWindow, icon);
// Cleanup
delete[] bits;
delete[] temp;
DeleteObject(hbitmap);
SDL_FreeSurface(icon);
}
Thank you to everyone who helped. I appreciate it. (If I'm missing anything in error testing or cleanup, please feel free to point it out and I'll update.)
bm.bmBits in your code, obtained from HICON, is most likely NULL. Use CopyImage with LR_CREATEDIBSECTION to access bmBits
HBITMAP hbitmap = (HBITMAP)CopyImage(ii.hbmColor, IMAGE_BITMAP,
bm.bmWidth, bm.bmHeight, LR_CREATEDIBSECTION);
BITMAP bm2;
GetObject(hbitmap, sizeof(BITMAP), &bm2);
...
DeleteObject(hbitmap);
Check bm2.bmBitsPixel to make sure it's 32-bit. Check bm2.bmBits to make sure it is not NULL.
void LoadIconFrom(std::string assembly, int rcId, const char* rcName)
{
...
ICONINFO ii;
GetIconInfo(hicon, &ii);
BITMAP bm;
GetObject(ii.hbmColor, sizeof(bm), &bm);
HBITMAP hbitmap = (HBITMAP)CopyImage(ii.hbmColor, IMAGE_BITMAP,
bm.bmWidth, bm.bmHeight, LR_CREATEDIBSECTION);
GetObject(hbitmap, sizeof(BITMAP), &bm);
if (bm.bmBitsPixel != 32) {error(); ...}
if (bm.bmBits == NULL) {error(); ...}
...
icon = SDL_CreateRGBSurface(SDL_SWSURFACE, bm.bmWidth, bm.bmHeight,
bm.bmBitsPixel, rmask, gmask, bmask, amask);
//copy bits upside down
if(SDL_MUSTLOCK(icon)) SDL_LockSurface(icon);
int wb = bm.bmWidthBytes;
BYTE* bits = icon->pixels;
BYTE* src = (BYTE*)bm.bmBits;
for(int j = 0; j < bm.bmHeight; j++)
memcpy(bits + j * wb, src + (bm.bmHeight - j - 1) * wb, wb);
if(SDL_MUSTLOCK(icon)) SDL_UnlockSurface(icon);
SDL_SetWindowIcon(mainWindow, icon);
// Cleanup
SDL_FreeSurface(icon);
DeleteObject(hbitmap);
}
I want to modify ffplay by hiding its SDL video player window. Rather, I want to grab the overlay as pixel-by-pixel bitmaps to be used elsewhere in my program.
Now ffplay can be simplified as below:
Create SDL_Surface *screen from SDL_SetVideoMode()
Create SDL_Overlay *bmp from SDL_CreateYUVOverlay() and associate it with screen
repeat until video ends
Decode movie frames and populate bmp
Render bmp onto screen using SDL_DisplayYUVOverlay()
Following hints from this article, I have replaced Step 1 as below:
/* Don't want video player window showing on screen
* int flags = SDL_HWSURFACE|SDL_ASYNCBLIT|SDL_HWACCEL;
* screen = SDL_SetVideoMode(w, h, 24, flags);
*/
Uint32 rmask, gmask, bmask, amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x00000000;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0x00000000;
#endif
screen = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 24, rmask, gmask, bmask, amask);
and Step 4 as
SDL_DisplayYUVOverlay(bmp, &rect);
SDL_SaveBMP(screen, filenameN); N++;
Issue:
If I modify only Step 4, the bitmap files are getting saved properly which is what I want, except that the video playing window is visible. On the other hand, if I modify Step 2 as well, the window gets successfully hidden the bitmaps are all blacked out.
I am new to SDL, so apart from just the solution, an explanation on why my approach does not work will be helpful.
Use SDL_putenv("SDL_VIDEODRIVER=dummy"); to use the dummy video driver, which produces no output.
Here's my code:
template<typename G, typename N> void load_xbm(G* g,N w, N h,unsigned char* data)
{
Uint32 rmask, gmask, bmask, amask;
/* SDL interprets each pixel as a 32-bit number, so our masks must depend
on the endianness (byte order) of the machine */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
SDL_Surface* s = g->backend_surface();
s = SDL_CreateRGBSurface(SDL_HWSURFACE,w,h,16,rmask,gmask,bmask,amask);
g->backend_surface( s );
for (N x = 0; x < w; x++)
{
for(N y = 0; y < h; y++)
{
g->put_pixel(x,y,data[y*x]);
}
}
SDL_Flip( s );
}
g->backend_surface() just returns an SDL_Surface* member in G.
w is the width of the xbm bitmap, h is the height, data is an array of unsigned chars containing the colors of every pixel.
g->put_pixel() is a simple wrap over of the putpixel method in SDL docs, which uses it's backend_surface as the first parameter to the example putpixel function.
Now when I execute it the program exits with 0x3. By debugging the code I've found that it exits on the call to the putpixel method, note that the putpixel method is working fine elsewhere. I've also found out that it only exits when the x and y arguments to putpixel are bigger than the original width and height of the Surface, but haven't I resized the surface using SDL_CreateRGBSurface to the required width and height?
Guessing in the wild here...
This SDL tutorial example (point 2.5) says that the Surface must be locked before calling this function. Is yours?
I've searched around using google but I'm completely confused on how to load an image (PNG in my case) from resource and then converting it to a bitmap in memory for use in my splash screen. I've read about GDI+ and libpng but I don't really know how to do what I want. Could anyone help?
GDI+ supports PNG directly. See here and here.
EDIT: The GDI+ documentation offers some advice for how to use GDI+ in a DLL. In your case, the best solution is probably to define initialisation and teardown functions that the client code is required to call.
I ended up using PicoPNG to convert the PNG to a two dimensional vector which I then manually contructed a bitmap from. My final code looked like this:
HBITMAP LoadPNGasBMP(const HMODULE hModule, const LPCTSTR lpPNGName)
{
/* First we need to get an pointer to the PNG */
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
unsigned int size = SizeofResource(hModule, found);
HGLOBAL loaded = LoadResource(hModule, found);
void* resource_data = LockResource(loaded);
/* Now we decode the PNG */
vector<unsigned char> raw;
unsigned long width, height;
int err = decodePNG(raw, width, height, (const unsigned char*)resource_data, size);
if (err != 0)
{
log_debug("Error while decoding png splash: %d", err);
return NULL;
}
/* Create the bitmap */
BITMAPV5HEADER bmpheader = {0};
bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
bmpheader.bV5Width = width;
bmpheader.bV5Height = height;
bmpheader.bV5Planes = 1;
bmpheader.bV5BitCount = 32;
bmpheader.bV5Compression = BI_BITFIELDS;
bmpheader.bV5SizeImage = width*height*4;
bmpheader.bV5RedMask = 0x00FF0000;
bmpheader.bV5GreenMask = 0x0000FF00;
bmpheader.bV5BlueMask = 0x000000FF;
bmpheader.bV5AlphaMask = 0xFF000000;
bmpheader.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmpheader.bV5Intent = LCS_GM_BUSINESS;
void* converted = NULL;
HDC screen = GetDC(NULL);
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
ReleaseDC(NULL, screen);
/* Copy the decoded image into the bitmap in the correct order */
for (unsigned int y1 = height - 1, y2 = 0; y2 < height; y1--, y2++)
for (unsigned int x = 0; x < width; x++)
{
*((char*)converted+0+4*x+4*width*y2) = raw[2+4*x+4*width*y1]; // Blue
*((char*)converted+1+4*x+4*width*y2) = raw[1+4*x+4*width*y1]; // Green
*((char*)converted+2+4*x+4*width*y2) = raw[0+4*x+4*width*y1]; // Red
*((char*)converted+3+4*x+4*width*y2) = raw[3+4*x+4*width*y1]; // Alpha
}
/* Done! */
return result;
}