【问题标题】:SDL2 resize a surfaceSDL2 调整表面大小
【发布时间】:2017-04-12 12:33:27
【问题描述】:

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

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

寻找解决方案有过类似的问题...:

当前 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 组合”其他变体也有类似错误:

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 等主要图像文件格式),或者如果这是推荐的方法,请提供任何帮助!

【问题讨论】:

  • 为了使用 blit 函数,您的硬件必须支持 GL_EXT_framebuffer_blit - 集成显卡和一些工作站 GPU 通常不支持。
  • 源图像的像素格式是什么(每像素位数和掩码)?当源和目标表面格式的组合没有blitting实现时产生的错误;我建议为每个通道目标表面创建“正常”8 位(RGB 为 24 位,RGBA 为 32 位)并将其插入。
  • @bruno-ferreira 谢谢,你说得对,不支持扩展,所以我安装了显卡驱动程序并得到了它,但不幸的是,如果没有解决这个问题
  • @keltar 感谢您的评论为我指明了正确的方向,我将在答案中详细说明

标签: c++ sdl-2


【解决方案1】:

TL;DR @kelter 的评论指出了我正确的方向,another stack overflow question 给了我一个解决方案:如果你先 Blit 到 32bpp 表面然后 BlitScaled 到另一个 32bpp 表面,它就可以工作.这适用于 8 位和 24 位深度的 png,32 位是不可见的 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
            // 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;
}

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

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

【讨论】:

    猜你喜欢
    • 2014-01-11
    • 2014-12-02
    • 1970-01-01
    • 1970-01-01
    • 2019-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-21
    相关资源
    最近更新 更多