SDL2 调整表面大小

SDL2 resize a surface

我们想通过使用 SDL_Image 加载图像来创建 SDL 表面,如果尺寸超过限制,则调整表面大小。

我们需要这样做的原因是 Raspbian SDL 从表面创建纹理时抛出错误('纹理尺寸限制为 2048x2048')。虽然这是一张非常大的图片,但我们不希望用户担心图片大小,我们希望为他们调整大小。尽管我们在 Windows 上没有遇到此限制,但我们正在尝试在 windows 上开发解决方案并且在调整纹理大小时遇到​​问题。

寻找解决方案有类似的问题...:[=​​17=]

当前的 SDL2 是否需要自定义 blitter 或 SDL_gfx(这些答案早于 SDL2 的 2013 年版本)? SDLRenderCopyEx 没有帮助,因为您需要生成我们的问题所在的纹理。

所以我们尝试了一些可用的 blitting 函数,例如 SDL_BlitScaled,下面是一个简单的程序来渲染 2500x2500 PNG 没有缩放:

#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;
}

更改 get_texture 函数,使其识别极限并尝试创建新表面:

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 失败并出现错误“Blit combination not supported”其他变体也有类似的错误:

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

然后我们尝试了一个非缩放的 blit...它没有抛出错误但只显示白色(不是清晰的颜色或图像中的颜色)。

SDL_BlitSurface(pSurface, &targetDimensions, pScaleSurface, &targetDimensions)

由于 blitting 函数不起作用,我们随后尝试使用与位图相同的图像(只是将 .png 导出为 .bmp),仍然使用 SDL_Image 加载文件,并且这两个函数都与 SDL_BlitScaled 按预期缩放

不确定这里出了什么问题(我们期望并需要对 .png 等主要图像文件格式的支持),或者如果这是推荐的方法,我们将不胜感激!

TL;DR @kelter 的评论为我指出了正确的方向, 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 建议在 blitting 之前先填充表面。

更新的 get_texture 函数:

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
            // 
            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
                // 
                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;
}

@kelter 的评论让我更仔细地观察了表面像素格式,位图以 24bpp 的速度工作,png 以 8bpp 的速度加载并且不工作。尝试将目标表面更改为 24 或 32 bpp,但这没有帮助。我们生成了具有自动检测位深度的 png,将其设置为 8 或 24,并在具有相同每像素位数的表面上执行 BlitScaled,尽管它不适用于 32。谷歌搜索 blit 转换错误导致来自@Petruza 的问答。

Update 写这个答案有点快,原来的解决方案处理 bmp 和 8 位和 24 位 png,但 32 位 png 没有渲染。 @Retired Ninja 回答另一个关于 Blit_Scaled 建议在调用函数之前填充表面并对其进行排序的问题,another question 与在可能与此相关的新表面上设置 alpha 有关(特别是如果您需要透明度),但填充纯色对我来说就足够了......现在。