提问人:Brainfog 提问时间:11/13/2023 最后编辑:Brainfog 更新时间:11/15/2023 访问量:224
为什么我的PNG文件颜色错误?因为SDL_RenderReadPixels?
Why do i get my PNG-file with wrong colors? Because of SDL_RenderReadPixels?
问:
在我的 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;
}
答:
我还使用 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 ); */
在我看来,你会在这里找到解决问题的关键。
评论
好吧,我不知道为什么我的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);
}
很抱歉浪费了你的时间。
评论