I'm using SDL 2.0 with C++ for 2D graphics, compressed image format loading, sound playback and so on. Consider the following code:
int main(int argc, char *args[])
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
SDL_Window *pWindow = SDL_CreateWindow("Access Violation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1920, 1080, SDL_WINDOW_FULLSCREEN);
SDL_Renderer *pRenderer = SDL_CreateRenderer(pWindow, -1, SDL_RENDERER_ACCELERATED);
Mix_Init(0);
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
Mix_Music *pMusic = Mix_LoadMUS("music.wav");
TTF_Init();
TTF_Font *pFont = TTF_OpenFont("font.ttf", 28);
bool quit = false;
SDL_Event ev;
while(!quit)
{
while(SDL_PollEvent(&ev) != 0)
{
switch(ev.type)
{
case SDL_QUIT:
quit = true;
break;
}
}
SDL_SetRenderDrawColor(pRenderer, 255, 255, 255, 255);
SDL_RenderClear(pRenderer);
for(int i = 0; i < 864; i++)
{
SDL_Color clr = { 0, 0, 0 };
SDL_Surface *pSurface = TTF_RenderText_Solid(pFont, "Hey!", clr);
SDL_Texture *pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);
SDL_Rect dstrect = { (int)(i/27) * 60, (i%27)*40, pSurface->w, pSurface->h };
SDL_RenderCopy(pRenderer, pTexture, 0, &dstrect);
SDL_DestroyTexture(pTexture);
SDL_FreeSurface(pSurface);
}
SDL_RenderPresent(pRenderer);
}
TTF_CloseFont(pFont);
Mix_FreeMusic(pMusic);
SDL_DestroyRenderer(pRenderer);
SDL_DestroyWindow(pWindow);
Mix_CloseAudio();
Mix_Quit();
IMG_Quit();
TTF_Quit();
SDL_Quit();
return 0;
}
What this basically does is that it just initializes SDL, loads a music, loads a font, and fills continuously the whole screen with text. Fine.
But!
If I add this line
...
SDL_Event ev;
Mix_PlayMusic(pMusic, 0);
while(!quit)
...
right here, text redrawing in the cycle
for(int i = 0; i < 864; i++)
{
...
}
starts to behave unpredictably. One of the SDL's function will shoot an access violation, after some time - between 1 and 30 seconds after start. On the other hand, would I omit text redrawing, music is playing fine.
Any ideas? Of course, all return values are checked. I've omitted checks because of the clarity of the code.
EDIT: Typos.
Related
I'm trying to test a simple SDL2 application. The app runs without any errors. However, no window shows up.
I'm using Ubuntu18.04 on a VMWare machine. I also tried to compile SDL from the source but got the same result. I am unsure if the cause of the problem is related to my OS or because of executing from a virtual machine.
Here is a simple application from docs (I just added SDL_GetNumDisplayModes to make sure the display mode is OK- returns 1)
// Using SDL and standard IO
#include <SDL2/SDL.h>
#include <stdio.h>
// Screen dimension constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
int main(int argc, char *args[])
{
// The window we'll be rendering to
SDL_Window *window = NULL;
// The surface contained by the window
SDL_Surface *screenSurface = NULL;
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
}
else
{
int res = SDL_GetNumDisplayModes(0);
printf("SDL_GetNumDisplayModes: %d\r\n", res);
// Create window
window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == NULL)
{
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
}
else
{
// Get window surface
screenSurface = SDL_GetWindowSurface(window);
// Fill the surface white
SDL_FillRect(screenSurface, NULL, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));
// Update the surface
SDL_UpdateWindowSurface(window);
// Hack to get window to stay up
SDL_Event e;
bool quit = false;
while (quit == false)
{
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
{
quit = true;
}
}
}
}
}
// Destroy window
SDL_DestroyWindow(window);
// Quit SDL subsystems
SDL_Quit();
return 0;
}
SDL_TTF 2.20.0 (with Harfbuzz enabled) introduced TTF_SetFontDirection() and TTF_SetFontScriptName() "for additional control over fonts using HarfBuzz".
I am trying to render some Arabic text, and I have set the font direction to RTL and the script name to "arSA\0", but the glyphs aren't joining up correctly.
Both TTF_SetFontDirection(ttf_font, TTF_DIRECTION_RTL) and TTF_SetFontScriptName(ttf_font, "arSA\0") return 0, which indicates success.
This is how I expected the text to appear:
And this is how it is appearing instead:
Can anybody tell me what I'm doing wrong?
Edit: Minimal reproducible example:
(Adapted from https://github.com/aminosbh/sdl2-ttf-sample/blob/master/src/main.c)
#include <SDL.h>
#include <SDL_ttf.h>
#include <string>
int main(int argc, char* argv[])
{
// Unused argc, argv
(void)argc;
(void)argv;
// Initialize SDL2
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("SDL2 could not be initialized!\n"
"SDL2 Error: %s\n", SDL_GetError());
return 0;
}
// Initialize SDL2_ttf
TTF_Init();
// Create window
SDL_Window* window = SDL_CreateWindow("SDL2_ttf sample",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
if (!window)
{
printf("Window could not be created!\n"
"SDL_Error: %s\n", SDL_GetError());
}
else
{
// Create renderer
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer)
{
printf("Renderer could not be created!\n"
"SDL_Error: %s\n", SDL_GetError());
}
else
{
// Declare rect of square
SDL_Rect squareRect;
// Square dimensions: Half of the min(SCREEN_WIDTH, SCREEN_HEIGHT)
squareRect.w = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;
squareRect.h = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;
// Square position: In the middle of the screen
squareRect.x = SCREEN_WIDTH / 2 - squareRect.w / 2;
squareRect.y = SCREEN_HEIGHT / 2 - squareRect.h / 2;
TTF_Font* font = TTF_OpenFont(FONT_PATH, 40);
if (!font) {
printf("Unable to load font: '%s'!\n"
"SDL2_ttf Error: %s\n", FONT_PATH, TTF_GetError());
return 0;
}
const auto fontDirectionSuccess = TTF_SetFontDirection(font, TTF_DIRECTION_RTL);
const auto fontScriptNameSuccess = TTF_SetFontScriptName(font, "arSA\0");
SDL_Color textColor = { 0x00, 0x00, 0x00, 0xFF };
SDL_Color textBackgroundColor = { 0xFF, 0xFF, 0xFF, 0xFF };
SDL_Texture* text = NULL;
SDL_Rect textRect;
// Arabic text to display
const wchar_t * wstr = L"كسول الزنجبيل القط";
// Convert unicode to multibyte
int wstr_len = (int)wcslen(wstr);
int num_chars = WideCharToMultiByte(CP_UTF8, 0, wstr, wstr_len, NULL, 0, NULL, NULL);
CHAR* strTo = (CHAR*)malloc((num_chars + 1) * sizeof(CHAR));
if (strTo)
{
WideCharToMultiByte(CP_UTF8, 0, wstr, wstr_len, strTo, num_chars, NULL, NULL);
strTo[num_chars] = '\0';
}
// Render text to surface
SDL_Surface* textSurface = TTF_RenderUTF8_Blended(font, strTo, textColor);
if (!textSurface) {
printf("Unable to render text surface!\n"
"SDL2_ttf Error: %s\n", TTF_GetError());
}
else {
// Create texture from surface pixels
text = SDL_CreateTextureFromSurface(renderer, textSurface);
if (!text) {
printf("Unable to create texture from rendered text!\n"
"SDL2 Error: %s\n", SDL_GetError());
return 0;
}
// Get text dimensions
textRect.w = textSurface->w;
textRect.h = textSurface->h;
SDL_FreeSurface(textSurface);
}
textRect.x = (SCREEN_WIDTH - textRect.w) / 2;
textRect.y = squareRect.y - textRect.h - 10;
// Event loop exit flag
bool quit = false;
// Event loop
while (!quit)
{
SDL_Event e;
// Wait indefinitely for the next available event
SDL_WaitEvent(&e);
// User requests quit
if (e.type == SDL_QUIT)
{
quit = true;
}
// Initialize renderer color white for the background
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
// Clear screen
SDL_RenderClear(renderer);
// Draw text
SDL_RenderCopy(renderer, text, NULL, &textRect);
// Update screen
SDL_RenderPresent(renderer);
}
// Destroy renderer
SDL_DestroyRenderer(renderer);
}
// Destroy window
SDL_DestroyWindow(window);
}
// Quit SDL2_ttf
TTF_Quit();
// Quit SDL
SDL_Quit();
return 0;
}
Edit 2:
I've replaced
TTF_SetFontDirection(font, TTF_DIRECTION_RTL);
TTF_SetFontScriptName(font, "arSA\0");
with the depracated:
TTF_SetDirection(HB_DIRECTION_RTL);
TTF_SetScript(HB_SCRIPT_ARABIC);
And everything works fine. I suspect "arSA\0" might be wrong, but the only documentation I can find says it should be a "null-terminated string of exactly 4 characters", with no indication or example of which 4 characters.
Instead of "arSA", you should use "Arab".
See https://github.com/harfbuzz/harfbuzz/blob/7a219ca9f0f8140906cb7fd3b879b5bf5259badc/src/hb-common.h#L514
Credits to commenters "1.8e9-where's-my-share m" & "skink".
I'm adding the answer here so you can accept is as answered.
My SDL program wont work. It was supposed to change the image when I pressed Up. However, it changes the image only when I click on the x in the Window
#include "SDL.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
int main(int argc, char* argv[])
{
enum KeyPressSurfaces {
KEY_PRESS_SURFACE_DEFAULT,
KEY_PRESS_SURFACE_UP,
KEY_PRESS_SURFACE_LEFT,
KEY_PRESS_SURFACE_RIGHT,
KEY_PRESS_SURFACE_DOWN,
KEY_PRESS_SURFACE_TOTAL
};
SDL_Init(SDL_INIT_VIDEO);
SDL_Event e;
SDL_Window* window = SDL_CreateWindow("antena1", // window's title
10, 25, // coordinates on the screen, in pixels, of the window's upper left corner
640, 420, // window's length and height in pixels
SDL_WINDOW_OPENGL);
SDL_Surface* key_press_surface[KEY_PRESS_SURFACE_TOTAL];
SDL_Surface* gImage = NULL;
SDL_Surface* gScreenSurface = NULL;
bool quit = false;
key_press_surface[KEY_PRESS_SURFACE_UP] = SDL_LoadBMP("hello_world.bmp");
gScreenSurface = SDL_GetWindowSurface(window);
gImage = SDL_LoadBMP("nick.bmp");
if (gImage == NULL) {
printf("Erro", SDL_GetError);
}
SDL_BlitSurface(gImage, NULL, gScreenSurface, NULL);
SDL_UpdateWindowSurface(window);
gScreenSurface = SDL_GetWindowSurface(window);
while (!quit) {
while (SDL_PollEvent(&e) == 0) {
if (e.type == SDL_QUIT) {
quit = true;
//SDL_DestroyWindow(window);#
}
else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_LEFT:
gImage = key_press_surface[KEY_PRESS_SURFACE_UP];
break;
default:
gScreenSurface = NULL;
break;
}
}
}
};
SDL_BlitSurface(gImage, NULL, gScreenSurface, NULL);
SDL_UpdateWindowSurface(window);
SDL_Delay(30000);
return 0;
}
https://wiki.libsdl.org/SDL_PollEvent
SDL_PollEvent returns 0 if there are no events available. So per the example in the above article, in order to get into the while statement for SDL_PollEvent, there must be events in the queue. However, your code only goes into the loop if there are NO events in the queue. So anything that happens once you get there is undefined.
IOW, just remove the "== 0".
I'm writing a program to rapidly capture images from one window, modify them, and output them to another window using Xlib with C++. I have this working via XGetImage:
#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
int main()
{
// Open default display
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Main event loop for new window
XImage *image;
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
image = XGetImage(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, AllPlanes, ZPixmap);
// Modify the image
if (image->data != nullptr)
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, TEST_SIZE, TEST_SIZE);
}
XDestroyImage(image);
}
// Goodbye
XCloseDisplay(display);
}
It generates output like this: https://streamable.com/hovg9
I'm happy to have gotten this far, but from what I've read the performance isn't going to scale very well because it has to allocate space for a new image at every frame. In fact, without the call to XDestroyImage at the end of the loop this program fills up all 16GB of memory on my machine in a matter of seconds!
It seems the recommended approach here is to to set up a shared memory space where X can write the contents of each frame and my program can subsequently read and modify them without the need for any extra allocation. Since the call to XShmGetImage blocks and waits for IPC I believe this means I won't have to worry about any concurrency issues in the shared space.
I've attempted to implement the XShmGetImage approach with the following code:
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
int main()
{
// Open default display
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Allocate shared memory for image capturing
Visual *visual = DefaultVisual(display, 0);
XShmSegmentInfo shminfo;
int depth = DefaultDepth(display, screen);
XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0666);
shmat(shminfo.shmid, nullptr, 0);
shminfo.shmaddr = image->data;
shminfo.readOnly = False;
XShmAttach(display, &shminfo);
// Main event loop for new window
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);
// Modify the image
if (image->data != nullptr) // NEVER TRUE. DATA IS ALWAYS NULL!
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
}
}
// Goodbye
XFree(image);
XCloseDisplay(display);
}
Somehow, the images are still being captured and sent to the new window, but the data pointer within the XImage is always null. I can't modify any of the contents and any usage of the XGetPixel and XPutPixel macros will fail. Notice in this video that none of the colors are being inverted like before: https://streamable.com/dckyv
This doesn't make any sense to me. Clearly the data is still being transferred between the windows, but where is it in the XImage structure? How can I access it via code?
It turns out I wasn't reading the signature for shmat correctly. It doesn't have a void return type, it returns a void pointer. This needs to be assigned directly to the XImage's data pointer in order for the data pointer to do anything.
In the call to XShmPutImage the shared memory location is referenced internally rather than the XImage's data pointer which is why this was still working even without utilizing the return value from shmat.
Here's the working code, along with a few other additions which I made for error handling and teardown:
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>
const int TEST_SIZE = 512;
static int handleXError(Display *display, XErrorEvent *event)
{
printf("XErrorEvent triggered!\n");
printf("error_code: %d", event->error_code);
printf("minor_code: %d", event->minor_code);
printf("request_code: %d", event->request_code);
printf("resourceid: %lu", event->resourceid);
printf("serial: %d", event->error_code);
printf("type: %d", event->type);
return 0;
}
int main()
{
// Open default display
XSetErrorHandler(handleXError);
Display *display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window rootWin = RootWindow(display, screen);
GC graphicsContext = DefaultGC(display, screen);
// Create new window and subscribe to events
long blackPixel = BlackPixel(display, screen);
long whitePixel = WhitePixel(display, screen);
Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
XMapWindow(display, newWin);
XSelectInput(display, newWin, ExposureMask | KeyPressMask);
// Allocate shared memory for image capturing
XShmSegmentInfo shminfo;
Visual *visual = DefaultVisual(display, screen);
int depth = DefaultDepth(display, screen);
XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777);
image->data = (char*)shmat(shminfo.shmid, 0, 0);
shminfo.shmaddr = image->data;
shminfo.readOnly = False;
XShmAttach(display, &shminfo);
XSync(display, false);
shmctl(shminfo.shmid, IPC_RMID, 0);
// Main event loop for new window
XEvent event;
bool exposed = false;
bool killWindow = false;
while (!killWindow)
{
// Handle pending events
if (XPending(display) > 0)
{
XNextEvent(display, &event);
if (event.type == Expose)
{
exposed = true;
} else if (event.type == NoExpose)
{
exposed = false;
} else if (event.type == KeyPress)
{
killWindow = true;
}
}
// Capture the original image
XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);
// Modify the image
if(image->data != nullptr) // NEVER TRUE. DATA IS ALWAYS NULL!
{
long pixel = 0;
for (int x = 0; x < image->width; x++)
{
for (int y = 0; y < image->height; y++)
{
// Invert the color of each pixel
pixel = XGetPixel(image, x, y);
XPutPixel(image, x, y, ~pixel);
}
}
}
// Output the modified image
if (exposed && killWindow == false)
{
XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
}
}
// Goodbye
XShmDetach(display, &shminfo);
XDestroyImage(image);
shmdt(shminfo.shmaddr);
XCloseDisplay(display);
}
We want to create an SDL surface by loading an image with SDL_Image and if the dimensions exceed a limit resize the surface.
The reason we need to do this is on Raspbian SDL throws an error creating a texture from the surface ('Texture dimensions are limited to 2048x2048'). Whilst that's a very large image we don't want users to be concerned about image size, we want to resize it for them. Although we haven't encountered this limit on Windows, we're trying to develop the solution on windows and having issues resizing the texture.
Looking for a solution there have been similar questions...:
2008 not SDL2 custom blitting
2010 use SDL_gfx
2008 can't be done use SDL_gfx, 2015 use SDL_BlitScaled, 2015 use SDL_RenderCopyEx
Is a custom blitter or SDL_gfx necessary with current SDL2 (those answers pre-date SDL2's 2013 release)? SDLRenderCopyEx doesn't help as you need to generate the texture which is where our problem occurs.
So we tried some of the available blitting functions like SDL_BlitScaled, below is a simple program to render a 2500x2500 PNG with no scaling:
#include <SDL.h>
#include <SDL_image.h>
#include <sstream>
#include <string>
SDL_Texture * get_texture(
SDL_Renderer * pRenderer,
std::string image_filename) {
SDL_Texture * result = NULL;
SDL_Surface * pSurface = IMG_Load(image_filename.c_str());
if (pSurface == NULL) {
printf("Error image load: %s\n", IMG_GetError());
}
else {
SDL_Texture * pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);
if (pTexture == NULL) {
printf("Error image load: %s\n", SDL_GetError());
}
else {
SDL_SetTextureBlendMode(
pTexture,
SDL_BLENDMODE_BLEND);
result = pTexture;
}
SDL_FreeSurface(pSurface);
pSurface = NULL;
}
return result;
}
int main(int argc, char* args[]) {
SDL_Window * pWindow = NULL;
SDL_Renderer * pRenderer = NULL;
// set up
SDL_Init(SDL_INIT_VIDEO);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
SDL_Rect screenDimensions;
screenDimensions.x = 0;
screenDimensions.y = 0;
screenDimensions.w = 640;
screenDimensions.h = 480;
pWindow = SDL_CreateWindow("Resize Test",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
screenDimensions.w,
screenDimensions.h,
SDL_WINDOW_SHOWN);
pRenderer = SDL_CreateRenderer(pWindow,
-1,
SDL_RENDERER_ACCELERATED);
IMG_Init(IMG_INIT_PNG);
// render
SDL_SetRenderDrawColor(
pRenderer,
0,
0,
0,
0);
SDL_RenderClear(pRenderer);
SDL_Texture * pTexture = get_texture(
pRenderer,
"2500x2500.png");
if (pTexture != NULL) {
SDL_RenderCopy(
pRenderer,
pTexture,
NULL,
&screenDimensions);
SDL_DestroyTexture(pTexture);
pTexture = NULL;
}
SDL_RenderPresent(pRenderer);
// wait for quit
bool quit = false;
while (!quit)
{
// poll input for quit
SDL_Event inputEvent;
while (SDL_PollEvent(&inputEvent) != 0) {
if ((inputEvent.type == SDL_KEYDOWN) &&
(inputEvent.key.keysym.sym == 113)) {
quit = true;
}
}
}
IMG_Quit();
SDL_DestroyRenderer(pRenderer);
pRenderer = NULL;
SDL_DestroyWindow(pWindow);
pWindow = NULL;
return 0;
}
Changing the get_texture function so it identifies a limit and tries to create a new surface:
SDL_Texture * get_texture(
SDL_Renderer * pRenderer,
std::string image_filename) {
SDL_Texture * result = NULL;
SDL_Surface * pSurface = IMG_Load(image_filename.c_str());
if (pSurface == NULL) {
printf("Error image load: %s\n", IMG_GetError());
}
else {
const int limit = 1024;
int width = pSurface->w;
int height = pSurface->h;
if ((width > limit) ||
(height > limit)) {
SDL_Rect sourceDimensions;
sourceDimensions.x = 0;
sourceDimensions.y = 0;
sourceDimensions.w = width;
sourceDimensions.h = height;
float scale = (float)limit / (float)width;
float scaleH = (float)limit / (float)height;
if (scaleH < scale) {
scale = scaleH;
}
SDL_Rect targetDimensions;
targetDimensions.x = 0;
targetDimensions.y = 0;
targetDimensions.w = (int)(width * scale);
targetDimensions.h = (int)(height * scale);
SDL_Surface *pScaleSurface = SDL_CreateRGBSurface(
pSurface->flags,
targetDimensions.w,
targetDimensions.h,
pSurface->format->BitsPerPixel,
pSurface->format->Rmask,
pSurface->format->Gmask,
pSurface->format->Bmask,
pSurface->format->Amask);
if (SDL_BlitScaled(pSurface, NULL, pScaleSurface, &targetDimensions) < 0) {
printf("Error did not scale surface: %s\n", SDL_GetError());
SDL_FreeSurface(pScaleSurface);
pScaleSurface = NULL;
}
else {
SDL_FreeSurface(pSurface);
pSurface = pScaleSurface;
width = pSurface->w;
height = pSurface->h;
}
}
SDL_Texture * pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);
if (pTexture == NULL) {
printf("Error image load: %s\n", SDL_GetError());
}
else {
SDL_SetTextureBlendMode(
pTexture,
SDL_BLENDMODE_BLEND);
result = pTexture;
}
SDL_FreeSurface(pSurface);
pSurface = NULL;
}
return result;
}
SDL_BlitScaled fails with an error 'Blit combination not supported' other variations have a similar error:
SDL_BlitScaled(pSurface, NULL, pScaleSurface, NULL)
SDL_BlitScaled(pSurface, &sourceDimensions, pScaleSurface, &targetDimensions)
SDL_LowerBlitScaled(pSurface, &sourceDimensions, pScaleSurface, &targetDimensions) // from the wiki this is the call SDL_BlitScaled makes internally
Then we tried a non-scaled blit... which didn't throw an error but just shows white (not the clear colour or a colour in the image).
SDL_BlitSurface(pSurface, &targetDimensions, pScaleSurface, &targetDimensions)
With that blitting function not working we then tried it with the same image as a bitmap (just exporting the .png as .bmp), still loading the file with SDL_Image and both those functions work with SDL_BlitScaled scaling as expected 😐
Not sure what's going wrong here (we expect and need support for major image file formats like .png) or if this is the recommended approach, any help appreciated!
TL;DR The comment from #kelter pointed me in the right direction and another stack overflow question gave me a solution: it works if you first Blit to a 32bpp surface and then BlitScaled to another 32bpp surface. That worked for 8 and 24 bit depth pngs, 32 bit were invisible again another stack overflow question suggested first filling the surface before blitting.
An updated get_texture function:
SDL_Texture * get_texture(
SDL_Renderer * pRenderer,
std::string image_filename) {
SDL_Texture * result = NULL;
SDL_Surface * pSurface = IMG_Load(image_filename.c_str());
if (pSurface == NULL) {
printf("Error image load: %s\n", IMG_GetError());
}
else {
const int limit = 1024;
int width = pSurface->w;
int height = pSurface->h;
if ((width > limit) ||
(height > limit)) {
SDL_Rect sourceDimensions;
sourceDimensions.x = 0;
sourceDimensions.y = 0;
sourceDimensions.w = width;
sourceDimensions.h = height;
float scale = (float)limit / (float)width;
float scaleH = (float)limit / (float)height;
if (scaleH < scale) {
scale = scaleH;
}
SDL_Rect targetDimensions;
targetDimensions.x = 0;
targetDimensions.y = 0;
targetDimensions.w = (int)(width * scale);
targetDimensions.h = (int)(height * scale);
// create a 32 bits per pixel surface to Blit the image to first, before BlitScaled
// https://stackoverflow.com/questions/33850453/sdl2-blit-scaled-from-a-palettized-8bpp-surface-gives-error-blit-combination/33944312
SDL_Surface *p32BPPSurface = SDL_CreateRGBSurface(
pSurface->flags,
sourceDimensions.w,
sourceDimensions.h,
32,
pSurface->format->Rmask,
pSurface->format->Gmask,
pSurface->format->Bmask,
pSurface->format->Amask);
if (SDL_BlitSurface(pSurface, NULL, p32BPPSurface, NULL) < 0) {
printf("Error did not blit surface: %s\n", SDL_GetError());
}
else {
// create another 32 bits per pixel surface are the desired scale
SDL_Surface *pScaleSurface = SDL_CreateRGBSurface(
p32BPPSurface->flags,
targetDimensions.w,
targetDimensions.h,
p32BPPSurface->format->BitsPerPixel,
p32BPPSurface->format->Rmask,
p32BPPSurface->format->Gmask,
p32BPPSurface->format->Bmask,
p32BPPSurface->format->Amask);
// 32 bit per pixel surfaces (loaded from the original file) won't scale down with BlitScaled, suggestion to first fill the surface
// 8 and 24 bit depth pngs did not require this
// https://stackoverflow.com/questions/20587999/sdl-blitscaled-doesnt-work
SDL_FillRect(pScaleSurface, &targetDimensions, SDL_MapRGBA(pScaleSurface->format, 255, 0, 0, 255));
if (SDL_BlitScaled(p32BPPSurface, NULL, pScaleSurface, NULL) < 0) {
printf("Error did not scale surface: %s\n", SDL_GetError());
SDL_FreeSurface(pScaleSurface);
pScaleSurface = NULL;
}
else {
SDL_FreeSurface(pSurface);
pSurface = pScaleSurface;
width = pSurface->w;
height = pSurface->h;
}
}
SDL_FreeSurface(p32BPPSurface);
p32BPPSurface = NULL;
}
SDL_Texture * pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);
if (pTexture == NULL) {
printf("Error image load: %s\n", SDL_GetError());
}
else {
SDL_SetTextureBlendMode(
pTexture,
SDL_BLENDMODE_BLEND);
result = pTexture;
}
SDL_FreeSurface(pSurface);
pSurface = NULL;
}
return result;
}
The comment from #kelter had me look more closely at the surface pixel formats, bitmaps were working at 24bpp, pngs were being loaded at 8bpp and not working. Tried changing the target surface to 24 or 32 bpp but that didn't help. We had generated the png with auto-detected bit depth, setting it to 8 or 24 and performing the BlitScaled on a surface with the same bits-per-pixel worked although it didn't work for 32. Googling the blit conversion error lead to the question and answer from #Petruza.
Update Was a bit quick writing up this answer, the original solution handled bmp and 8 and 24 bit pngs but 32 bit pngs weren't rendering. #Retired Ninja answer to another question about Blit_Scaled suggested filling the surface before calling the function and that sorts it, there's another question related to setting alpha on new surfaces that may be relevant to this (particularily if you needed transparency) but filling with a solid colour is enough for me... for now.