为什么我的PNG文件颜色错误?因为SDL_RenderReadPixels?

Why do i get my PNG-file with wrong colors? Because of SDL_RenderReadPixels?

提问人:Brainfog 提问时间:11/13/2023 最后编辑:Brainfog 更新时间:11/15/2023 访问量:224

问:

在我的 TexturWrapper 类中,我需要一种方法将主纹理或其一部分保存到 PNG 文件中。

SDL_SaveBMP 和 IMG_SavePNG 只能将SDL_Surface保存到文件中,而不是SDL_Texture。

主纹理可以是SDL_TEXTUREACCESS_STREAMING或SDL_TEXTUREACCESS_TARGET。 由于“目标”的可能性,我无法直接使用SDL_LockTexture访问纹理像素。 但是我们需要像素数据来从纹理中创建表面。

...从理论上讲,它以我的方式工作,但我无法获得正确的颜色。例如,我的一个测试文件在原始文件中具有 backgroundcolor cyan,这意味着在SDL_Color中:{0, 255, 255, 255},但 png 文件的颜色错误,而背景是黄色。黄色SDL_Color为 {255, 255, 0 , 255}。因此,我首先更改了红色和蓝色通道的蒙版。有趣的是,在那次更改之后,它仍然是黄色背景。 我尝试了其他组合,但没有效果。有些人肯定返回了“未知像素格式”SDL_Error。

我将蒙版恢复正常,并编写了一个函数来手动更改表面的像素:

void rearrangePixels(Uint8* pixelData, int width, int height, Uint32 rmask, Uint32 gmask, Uint32 bmask, Uint32 amask)

这些是 R、G、B 和 A 的所有可能组合:

RGBA
RGAB
RBGA
RBAG
RAGB
RABG
GRBA
GRAB
GBRA
GBAR
GARB
GABR
BRGA
BRAG
BGRA
BGAR
BARG
BAGR
ARGB
ARBG
AGRB
AGBR
ABRG
ABGR

...我只有 2 个不同的 png 文件产品。1. 黄色背景或 2.100%透明,所以看不见的画面。

我试图自己解决这个问题,谷歌和大量使用 phind.com 现在已经一个月了。

我也试图在旧帖子和 stackoverflow 中的答案中找到解决方案,并且我合并了其他用户的一些代码,例如我的代码中的 user1902824(甚至在 SDL2 中不存在SDL_CreateRGBSurfaceWithFormatFrom)...... 但我的问题仍然存在。

我正在使用 C4droid,直接在 C++17 的 Android 和 SDL2 + SDL_Image 上使用。 我唯一的硬件是一台 3-4 岁的 Huawaii 平板电脑,配备 3GB Ram 和 Android-8。这就是为什么 C4droid 不能是最新版本的原因,所以我的 SDL2.

这是我的代码的简化版本:

(您需要先初始化 sdl2 和一个名为“myRenderer”的渲染器作为全局变量)

class TexturWrapper
{
      
    public:
    
        // Init Variables
        TexturWrapper();

        // Destructor
        ~TexturWrapper();

        // Load ...with colorKey?
        bool Laden( std::string pfad , bool CKAktiv = 0 , unsigned char CKr = 0 , unsigned char CKg = 0 , unsigned char CKb = 0  );

        // Blank-Texture
        bool Blank( int BREITE , int HOEHE , char ta ); // (w, h, 's' = streaming and 't' = target)

        // Size of Image
        int getBreite(); //width
        int getHoehe(); //highth

        // Set as Rendertarget
        void Renderziel();

        // Reset Rendertarget
        void RenderzielReset();

        // Render Texture to specific place in specific angle
        void Rendern( int x , int y , SDL_Rect* clip = NULL , double angle = 0.0 , SDL_Point* center = NULL ,  SDL_RendererFlip flip = SDL_FLIP_NONE , SDL_Rect* rQuad = NULL , bool centerScale = true);

         // Pixel-Manipulation
         bool SaveImage(const char *filename, SDL_Rect* clip = NULL, SDL_Rect* rQuad = NULL);
         SDL_Surface* flip_surface_vertical(SDL_Surface* sfc);

        //For use if the Rendersize is adjustet (adjustRectToPreserveAspectRatio)
        int getPositionX();
        int getPositionY();
        int getRenderW();
        int getRenderH();

        // destroy texture
        void free();
        // Object clear
        void leeren();

    private:

void rearrangePixels(Uint8* pixelData, int width, int height, Uint32 rmask, Uint32 gmask, Uint32 bmask, Uint32 amask) {
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            // ptr to pixel
            Uint8* pixel = pixelData + y * width * 4 + x * 4;
                // Extract pixeldata
                Uint8 g = (*pixel & rmask) >> 24;
                Uint8 r = (*pixel & gmask) >> 16;
                Uint8 b = (*pixel & bmask) >> 8;
                Uint8 a = *pixel & amask;
                // new order of color-channels
                *pixel = (r << 24) | (g << 16) | (b << 8) | a;
        }
    }
}

void adjustRectToPreserveAspectRatio(SDL_Rect* srcRect, SDL_Rect* dstRect) {
    float srcAspectRatio = (float)srcRect->w / srcRect->h;
    float dstAspectRatio = (float)dstRect->w / dstRect->h;
    if (srcAspectRatio > dstAspectRatio) {
        // if texture.w > detination.w, change hight
        dstRect->h = dstRect->w / srcAspectRatio;
    } else if (srcAspectRatio < dstAspectRatio) {
        // if texture.h > destination.h, change width
        dstRect->w = dstRect->h * srcAspectRatio;
    }
}
    
        // Main-Texture
        SDL_Texture* ImageTextur;

        // Renderdestination
        SDL_Rect renderQuad = { 0 , 0 , 0 , 0 };

        void* Pixels;
        int Pitch;

        // Image dimensions
        int ImageBreite;
        int ImageHoehe;
        
        // ColorKey
        bool CKAktiv;
        unsigned char CKr;
        unsigned char CKg;
        unsigned char CKb;
        
};



TexturWrapper::TexturWrapper() 
{
    // Initialise Variables
    ImageTextur = NULL;
    ImageBreite = 0;
    ImageHoehe = 0;
    Pixels = NULL;
    Pitch = 0;
}

TexturWrapper::~TexturWrapper()
{
    leeren();
}

bool TexturWrapper::Laden( std::string pfad , bool CKAktiv , unsigned char CKr , unsigned char CKg , unsigned char CKb )
{
    free();

    // New Texture
    SDL_Texture* NeueTextur = NULL;

    // Load Image from path
    SDL_Surface* FileSurface = IMG_Load( pfad.c_str() );
    if( FileSurface == NULL )
    {
            char txt[ 64 ] = "";
              sprintf( txt , "Could not load image: %s! SDL_image Error: %s\n" , pfad.c_str() , IMG_GetError() );
                  std::cout << txt;
                  exit( EXIT_FAILURE );
    }
    else
    {

        SDL_Surface* formattedSurface = SDL_ConvertSurfaceFormat( FileSurface , SDL_PIXELFORMAT_RGBA8888 , 0 );

        NeueTextur = SDL_CreateTexture( myRenderer , SDL_PIXELFORMAT_RGBA8888 , SDL_TEXTUREACCESS_STREAMING , formattedSurface->w , formattedSurface->h );

        ImageBreite = formattedSurface->w;
        ImageHoehe = formattedSurface->h;

        SDL_SetTextureBlendMode( NeueTextur , SDL_BLENDMODE_BLEND );

        SDL_LockTexture( NeueTextur , &formattedSurface->clip_rect , &Pixels , &Pitch );

            memcpy( Pixels , formattedSurface->pixels , formattedSurface->pitch * formattedSurface->h );
      
            // Pixels-Data into editable Format
            Uint32* pixels = (Uint32*)Pixels;
            int pixelCount = ( Pitch / 4 ) * ImageHoehe;
      
            // ColorKey Image
            if ( CKAktiv == 1 ) {
                    // Map Colors               
                    Uint32 colorKey = SDL_MapRGB( formattedSurface->format , CKr , CKg , CKb );
                    Uint32 transparent = SDL_MapRGBA( formattedSurface->format , 0x00 , 0xFF , 0xFF , 0x00 );
            
                    // ColorKey pixels
                    for( int i = 0 ; i < pixelCount ; ++i ) {
                        if( pixels[ i ] == colorKey ) {
                            pixels[ i ] = transparent;
                        }
                    }
                  }

        SDL_UnlockTexture( NeueTextur );
        Pixels = NULL;


        // clean up
        SDL_FreeSurface( formattedSurface );
        SDL_FreeSurface( FileSurface );
    }

    ImageTextur = NeueTextur;
    NeueTextur = NULL;
    SDL_DestroyTexture(NeueTextur);
    
    CKAktiv = 0;
    CKr = 0;
    CKg = 0;
    CKb = 0;
    
    return ImageTextur != NULL; 
}

void TexturWrapper::free()
{
    if( ImageTextur != NULL )
    {
        SDL_DestroyTexture( ImageTextur );
        ImageTextur = NULL;
        ImageBreite = 0;
        ImageHoehe = 0;
        Pixels = NULL;
        Pitch = 0;
    }
}

void TexturWrapper::leeren()
{
        SDL_DestroyTexture( ImageTextur );
        ImageTextur = NULL;
        ImageBreite = 0;
        ImageHoehe = 0;
        Pixels = NULL;
        Pitch = 0;
        CKAktiv = 0;
        CKr = 0;
        CKg = 0;
        CKb = 0;
}

void TexturWrapper::Rendern( int x , int y , SDL_Rect* clip , double angle , SDL_Point* center , SDL_RendererFlip flip , SDL_Rect* rQuad , bool centerScale )
{ //------------------------------------------------K1.10

      if (rQuad != NULL)
      {
            //the picture
            SDL_Rect* TextureQuad = new SDL_Rect;
            TextureQuad->x = 0;
            TextureQuad->y = 0;
            TextureQuad->w = ImageBreite;
            TextureQuad->h = ImageHoehe;
            // Render area
            renderQuad.x = rQuad->x;
            renderQuad.y = rQuad->y;
            renderQuad.w = rQuad->w;
            renderQuad.h = rQuad->h;
            // scale, by keeping propotions
            adjustRectToPreserveAspectRatio(TextureQuad, &renderQuad);
            if (centerScale) {
                  // center picture in the middle on to renderpoint
                  renderQuad.x -= renderQuad.w / 2;
            }
            delete TextureQuad;
      }
      else {
            // Set render position and size on screen
            renderQuad = { x , y , ImageBreite , ImageHoehe };
      }

      // Set Clip_Rendering Dimensions and fit target-rect to it if clip exists
      if (clip != NULL)
      {
          renderQuad.w = clip->w;
          renderQuad.h = clip->h;
          adjustRectToPreserveAspectRatio(clip, &renderQuad);
      }

      SDL_RenderCopyEx( myRenderer , ImageTextur , clip , &renderQuad , angle , center , flip );
}

int TexturWrapper::getBreite()
{
    return ImageBreite;
}

int TexturWrapper::getHoehe()
{
    return ImageHoehe;
}

int TexturWrapper::getPositionX()
{
    return renderQuad.x;
}

int TexturWrapper::getPositionY()
{
    return renderQuad.y;
}

int TexturWrapper::getRenderW()
{
    return renderQuad.w;
}

int TexturWrapper::getRenderH()
{
    return renderQuad.h;
}

void TexturWrapper::Renderziel()
{
    SDL_SetRenderTarget( myRenderer , ImageTextur );
}

void TexturWrapper::RenderzielReset()
{
    SDL_SetRenderTarget( myRenderer , NULL );
}

bool TexturWrapper::Blank( int b , int h , char ta )
{
    // Blank Texture for Streaming....
      if ( ta == 's' ) {
    ImageTextur = SDL_CreateTexture( myRenderer , SDL_PIXELFORMAT_RGBA8888 , SDL_TEXTUREACCESS_STREAMING , b , h );
      }
      else if ( ta == 't' ) { // ...or Target access
    ImageTextur = SDL_CreateTexture( myRenderer , SDL_PIXELFORMAT_RGBA8888 , SDL_TEXTUREACCESS_TARGET , b , h );
      }
    if( ImageTextur == NULL )
    {
            char txt[ 64 ] = "";
                sprintf( txt , "Could not create blank texture! SDL Error: %s\n" , SDL_GetError() );
                  std::cout << txt;
                  exit( EXIT_FAILURE );
    }
    else
    {
        ImageBreite = b;
        ImageHoehe = h;
    }
      SDL_SetTextureBlendMode( ImageTextur , SDL_BLENDMODE_BLEND );
    return ImageTextur != NULL;
}

// This is the method i try to get to work correktly!
bool TexturWrapper::SaveImage(const char *fname, SDL_Rect* clipRect, SDL_Rect* rQuadRect)
{
    // test clipRect and rQuadRect) for NULL
    SDL_Rect clip = {0, 0, ImageBreite, ImageHoehe};
    SDL_Rect rQuad = {0, 0, ImageBreite, ImageHoehe};
    if (clipRect) {
        clip = *clipRect;
    }
    if (rQuadRect) {
        rQuad = *rQuadRect;
    }

    SDL_Texture *ren_tex;
    SDL_Surface *surface;
    int st;
    int w;
    int h;
    int format;
    void *pixels;

    pixels  = NULL;
    surface = NULL;
    ren_tex = NULL;
    format  = SDL_PIXELFORMAT_RGBA8888;

    /* Get information about texture we want to save */
    st = SDL_QueryTexture(ImageTextur, NULL, NULL, &w, &h);
    if (st != 0) {
            char txt[ 64 ] = "";
             sprintf( txt , "SDL_QueryTexture failure!\n" );
               std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }

    ren_tex = SDL_CreateTexture(myRenderer, format, SDL_TEXTUREACCESS_TARGET, rQuad.w, rQuad.h);
    if (!ren_tex) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not create Rendertexture!\n" );
                std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }

    /*
     * Initialize canvas, then copy texture to a target whose pixel data we 
     * can access
     */
    st = SDL_SetRenderTarget(myRenderer, ren_tex);
    if (st != 0) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not set rendertarget!\n" );
                    std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }

    // Set the clip rectangle for rendering
    SDL_RenderSetClipRect(myRenderer, &clip);

    SDL_SetRenderDrawColor(myRenderer, 0x00, 0x00, 0x00, 0x00);
    SDL_RenderClear(myRenderer);

    // Render the texture to the render target
    st = SDL_RenderCopy(myRenderer, ImageTextur, &clip, &rQuad);
    if (st != 0) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not copy Texturedata!\n" );
                std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }

    /* Create buffer to hold texture data and load it */
    pixels = malloc(rQuad.w * rQuad.h * SDL_BYTESPERPIXEL(format));
    if (!pixels) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not malloc in memory!\n" );
               std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }
    SDL_RenderPresent(myRenderer);
    st = SDL_RenderReadPixels(myRenderer, &clip, format, pixels, rQuad.w * SDL_BYTESPERPIXEL(format));
    if (st != 0) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not read pixeldata: %s\n", SDL_GetError() );
                  std::cout << txt;
               exit( EXIT_FAILURE );
               return false;
    }

      // Create mask
      Uint32 rmask, gmask, bmask, amask;
      if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
          Uint32 rmask = 0x000000ff;
          Uint32 gmask = 0x0000ff00;
          Uint32 bmask = 0x00ff0000;
          Uint32 amask = 0xff000000;
      } else {
          Uint32 rmask = 0xff000000;
          Uint32 gmask = 0x00ff0000;
          Uint32 bmask = 0x0000ff00;
          Uint32 amask = 0x000000ff;
      }

    /Cooy Pixeldata into surface
    surface = SDL_CreateRGBSurfaceFrom(pixels, rQuad.w, rQuad.h, 32, rQuad.w * SDL_BYTESPERPIXEL(format), rmask, gmask, bmask, amask);
    if (!surface) {
            char txt[ 64 ] = "";
             sprintf( txt , "Could not create surface: %s\n", SDL_GetError() );
               std::cout << txt;  
               exit( EXIT_FAILURE );
               return false;
    }


///////////////
// lock if needet
if (SDL_MUSTLOCK(surface)) {
    SDL_LockSurface(surface);
}

// manipulate pixeldata
rearrangePixels((Uint8*)surface->pixels, surface->w, surface->h, rmask, gmask, bmask, amask);

// Unlock if needet
if (SDL_MUSTLOCK(surface)) {
    SDL_UnlockSurface(surface);
}
//////////////

SDL_Surface* formatSurf = flip_surface_vertical( surface ); // because RenderReadPixels returns upside downdown data

           if (IMG_SavePNG(formatSurf, fname) != 0) {
                  char txt[ 64 ] = "";
                  sprintf( txt , "Could not save png-file %s\n" , SDL_GetError() );
                  std::cout << txt; 
                  exit( EXIT_FAILURE );
                  return false;
          }
   }

    SDL_SetRenderTarget(myRenderer, NULL);
    SDL_FreeSurface(surface);
    SDL_FreeSurface(formatSurf);
    ::free(pixels); // Global free() from cstdlib.h
    SDL_DestroyTexture(ren_tex);
    return true;
}

SDL_Surface* TexturWrapper::flip_surface_vertical(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);
    const auto pitch = sfc->pitch;
    const auto pxlength = pitch*(sfc->h - 1);
    auto pixels = static_cast<unsigned char*>(sfc->pixels) + pxlength;
    auto rpixels = static_cast<unsigned char*>(result->pixels) ;
    for(auto line = 0; line < sfc->h; ++line) {
        memcpy(rpixels,pixels,pitch);
        pixels -= pitch;
        rpixels += pitch;
    }
    return result;
}
安卓 C++ C++17 SDL-2 SDL 映像

评论

0赞 Robert 11/13/2023
PNG 文件使用什么颜色模型:调色板、RGB、RGB+传输、RGB+alpha 和 8 位和 16 位的 RBG?libpng.org/pub/png/book/chapter08.html#png.ch08.div.5
0赞 Robert 11/13/2023
有很多工具可以显示图像数据,例如 superuser.com/a/716357/62676。或者,您可以使用多个图像编辑器打开文件,然后使用另存为功能写入文件的新版本。我假设很多编辑会更改PNG颜色格式,甚至会问你该怎么做。检查创建的文件在加载到您的应用程序中时是否显示正确的颜色。
0赞 Brainfog 11/14/2023
遗憾的是,SDL_image库中的 IMG_SavePNG 函数没有任何其他参数来更改设置,例如颜色模型。它只需要两个参数:要保存的SDL_Surface和应保存文件的文件路径。PNG 规范仅支持 RGB 颜色模型,因此无法将 PNG 图像保存在其他颜色模型中。PNG 图像中的颜色通道顺序始终为 RGBA,SDL_image库遵循此规范。IMG_SavePNG始终将 Alpha 通道(透明度)数据保存在 PNG 图像中。
0赞 Brainfog 11/14/2023
没有人知道为什么我只得到 png 文件的 2 个可能版本?为什么颜色与原始 ImageTextur 纹理中的颜色不同?我在监督什么?
0赞 Brainfog 11/17/2023
嗯......你为什么要投票否决我?我只是需要我的项目帮助。你不喜欢我的写作风格吗?如果是这样,我很抱歉。
0赞 Jesper Juhl 11/27/2023
“PNG 规范仅支持 RGB 颜色模型” - 不完全正确,您应该仔细阅读 w3.org/TR/png-3

答:

-1赞 El_Codepone 11/25/2023 #1

我还使用 sdl2 编程。我成功地在 Windows 上将 SaveImage 方法测试为纯函数,这意味着结果采用正确的颜色排列并且没有失真。因此,您认为SDL_RenderReadPixels因硬件而提供不同回报的假设可能是正确的。

数据的测定为您揭示了什么?

/* // 获取有关SDL_RenderReadPixels输出格式的信息 字符txt[ 256 ]; sprintf( txt , “\n每像素位数: %d\n 每像素字节数: %d\n 掩码: %08X\n 掩码: %08X\n 掩码: %08X\n 掩码: %08X\n” , format->BitsPerPixel, format->BytesPerPixel, format->Rmask, format->Gmask, format->Bmask, format->Amask); MessageBox( “Das 格式:” , txt ); */

在我看来,你会在这里找到解决问题的关键。

评论

0赞 Brainfog 11/26/2023
嗯。。位 pp:32 ,字节 pp 4 ,Rmask 00FF0000 ,Gmask 0000FF00,Bmask 000000FF 和 Amask 一直是 000000000,但现在FF000000,我不明白。在保存 png 之前,我尝试确保使用此数据创建表面。同样的问题。我不认识任何毛皮。
1赞 Community 11/26/2023
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。
-1赞 Brainfog 11/27/2023 #2

好吧,我不知道为什么我的rearrangePixels方法不起作用。nvm!

出于我的借口,我甚至第一个想到的是 r 通道和 b 通道是交换的,只需要再次交换。

但这正是我尝试使用 rearrangePixels 方法做的事情。

解决方案是只交换新表面上的通道。

这是我的解决方案的完整代码:

void TexturWrapper::SaveImage(const char* file_name, SDL_Rect* clipRect, SDL_Rect* rQuadRect) {
// test clipRect and rQuadRect for NULL
SDL_Rect clip = {0, 0, ImageBreite, ImageHoehe};
SDL_Rect rQuad = {0, 0, ImageBreite, ImageHoehe};
if (clipRect) {
    clip = *clipRect;
}
if (rQuadRect) {
    rQuad = *rQuadRect;
}
SDL_Texture* rQuadTexture = SDL_CreateTexture(myRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, rQuad.w, rQuad.h); // final-size texture
SDL_SetRenderTarget(myRenderer, rQuadTexture); // make it target
SDL_RenderCopy(myRenderer, ImageTextur, &clip, NULL); // render clip to it

// surface in rQuad size
SDL_Surface* surface = SDL_CreateRGBSurface(0, rQuad.w, rQuad.h, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);

// get the pixels of rQuadTexture
SDL_RenderReadPixels(myRenderer, NULL, surface->format->format, surface->pixels, surface->pitch);

SDL_PixelFormat* format = surface->format;

  // lock if needet
  if (SDL_MUSTLOCK(surface)) {
      SDL_LockSurface(surface);
  }

  // THIS IS THE SOLUTION!
  SDL_Surface* swapped_surface = SDL_CreateRGBSurfaceFrom(
     (void*)surface->pixels,
     surface->w,
     surface->h,
     surface->format->BytesPerPixel * 8,
     surface->pitch,
     format->Bmask, // Rmask = Bmask from the original surface
     format->Gmask, // Gmask = Gmask
     format->Rmask, // Bmask = Rmask from the original surface
     format->Amask // Amask = Amask
  );

  
  // Unlock if needet
  if (SDL_MUSTLOCK(surface)) {
      SDL_UnlockSurface(surface);
  }

SDL_Surface* flipSurf = flip_surface_vertical( swapped_surface ); // because RenderReadPixels returns upsidedown data

IMG_SavePNG(flipSurf, file_name);
SDL_FreeSurface(surface);
SDL_FreeSurface(swapped_surface);
SDL_FreeSurface(flipSurf);
SDL_SetRenderTarget(myRenderer, NULL);
}

很抱歉浪费了你的时间。