(希望你喜欢阅读。我很抱歉内容太多......)
在我的 TexturWrapper 类中,我需要一种方法将主纹理或其一部分保存到 PNG 文件中。 (Textur 是德语,对于我的代码中的此类内容再次表示抱歉。)
IMG_SavePNG 只能将 SDL_Surface 保存到文件,而不是 SDL_Texture。
我的类的主要纹理可以是 SDL_TEXTUREACCESS_STREAMING 或 SDL_TEXTUREACCESS_TARGET。 由于目标可能性,我无法直接使用 SDL_LockTexture 访问纹理像素。 但我们需要像素数据来从纹理创建表面。
...理论上它按我的方式工作,但我无法获得正确的颜色。例如,我的一个测试文件在原始背景颜色中为青色,这意味着在 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% 透明且看不见的图片。
我尝试自己解决这个问题,使用 Google 并大量使用 phind.com 一个月了。
我也尝试在stackoverflow中的旧帖子和答案中找到解决方案,并且我合并了其他用户的一些代码,例如我的代码中的user1902824(即使我的SDL2版本中没有“SDL_CreateRGBSurfaceWithFormatFrom”)。 .
但我的问题仍然存在。
我正在使用 C4droid,直接在 C++17 中的 Android 上使用 SDL2 + SDL_Image。 我唯一的硬件是一台使用了 3-4 年的华为平板电脑,配备 3GB RAM 和 Android-8。这就是为什么 C4droid 不能是最新版本以及我的 SDL2 的原因。
编辑:
以我自己的技术水平无法解决这个问题。我真的尝试了我发现或能想到的一切。
也许还有其他一些我现在不记得了。
我不确定,但我相信SDL_RenderReadPixels返回不同的数据, 取决于系统和/或硬件!?
无论如何,我一个人无法应对。 所以我会尽力为您提供更多信息和更好的代码, 您可以在 c4droid 中编译出一个 cpp 文件。 (您需要更改图像的路径)
顺便说一句,这是我使用的原始图像: 当我将文件加载到表面并直接使用 IMG_SavePNG 保存时,它是相同的!
这是我在代码中首先使用 SDL_RenderReadPixels 时得到的结果:
最后(对我来说最令人惊讶的是)这是我使用我的 无论通道组合如何,rearrangePixels 方法:
这是我的代码的简化和更正版本(您可以将整个代码逐步复制并粘贴到一个 cpp 文件中,以使用 c4droid 进行编译):
首先,变量、SDL2、SDL_Image 和一些便利函数的初始化:
#include <SDL2/SDL.h>
#include <SDL_image.h>
#include <string>
// Global variables
static SDL_Window *myWindow;
static SDL_Renderer *myRenderer;
// call a message box
void MessageBox( std::string header , std::string message )
{
SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_INFORMATION , header.c_str() , message.c_str() , NULL );
}
// clean up
void sdlExit( void )
{
SDL_DestroyWindow( myWindow );
IMG_Quit();
SDL_DestroyRenderer( myRenderer );
SDL_Quit();
return;
}
// returns the aktual display mode in a sdl_rect
SDL_Rect GetDisplayMode()
{
SDL_DisplayMode displayM;
SDL_GetCurrentDisplayMode(0, &displayM);
// get a percent of the display hight
int percentY = displayM.h / 100;
// trick the android bars out for fullscreen immersive-mode
int adjustedY = - (percentY * 3); // Set y-coordinate 3% below the upperbound
int adjustedHeight = displayM.h + (percentY * 6); // hight + 6%
SDL_Rect displayMode = {0, adjustedY, displayM.w, adjustedHeight};
return displayMode;
}
// Initialize SDL2, SDL_Image and open a window
bool sdlInit( const char* title)
{
// Initialize SDL2
if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER ) != 0 ) {
char txt[ 64 ];
sprintf( txt , "Could not initialisieren SDL2: %s.\n" , SDL_GetError() );
MessageBox( "In Function sdlInit:" , txt );
return false;
}
atexit( SDL_Quit );
// Device display mode
SDL_Rect displayMode = GetDisplayMode();
// the window:
myWindow = SDL_CreateWindow(
title, // title "..."
SDL_WINDOWPOS_UNDEFINED, // initialize x position
SDL_WINDOWPOS_UNDEFINED, // initialize y position
displayMode.w, // width, in pixel
displayMode.h, // hight, in pixel
SDL_WINDOW_FULLSCREEN_DESKTOP ); // windowmode or 0
// Start Immersive Full-Screen-Mode
SDL_SetHint(SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1");
SDL_SetWindowFullscreen(myWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
// Check window
if ( myWindow == NULL ) {
char txt[ 64 ];
sprintf( txt , "Could not create window: %s\n" , SDL_GetError() );
MessageBox( "In Function sdlInit:" , txt );
return false;
}
// Initialize Renderer,
myRenderer = SDL_CreateRenderer( myWindow , -1 , SDL_RENDERER_ACCELERATED );
SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY , "linear" );
SDL_RenderSetLogicalSize( myRenderer , displayMode.w , displayMode.h );
// Check Renderer
if ( myRenderer == NULL ) {
char txt[ 64 ];
sprintf( txt , "Could not initialize renderer: %s\n" , SDL_GetError() );
MessageBox( "In Function sdlInit:" , txt );
return false;
}
// Set blending
SDL_SetRenderDrawBlendMode( myRenderer , SDL_BLENDMODE_BLEND );
// Initialize SDL_Img--------------------------------F1.2
int imgFlags = IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF;
int initted = IMG_Init( imgFlags );
if (( initted & imgFlags ) != imgFlags ) {
char txt[ 64 ];
sprintf( txt , "IMG_Init: Failed to init required support: %s\n", IMG_GetError() );
MessageBox( "In Function sdlInit:" , txt );
return false;
}
return true;
}
现在是 TexturWrapper 类主体:
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 (texture-clip) to specific place in specific angle in specific size
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
SDL_Surface* flip_surface_vertical(SDL_Surface* sfc);
// Put ImageTextur (ImageTextur - clip) to a file
void SaveImage(const char *filename, SDL_Rect* clip = NULL, SDL_Rect* rQuad = NULL);
//For use if the Rendersize is adjustet (adjustRectToPreserveAspectRatio)
int getPositionX();
int getPositionY();
int getRenderW();
int getRenderH();
// destroy just texture
void free();
// Object clear
void leeren();
private:
// tried to get a correction of the result from SDL_RenderReadPixels()
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) {
// pointer to one pixel
Uint8* pixel = pixelData + y * width * 4 + x * 4;
// extract pixeldata
Uint32 pixelValue = *(Uint32*)pixel;
// pixelValue = pixelValue << 8; // Ignore first byte if this would be the problem
Uint8 r = (pixelValue & rmask) >> 24;
Uint8 g = (pixelValue & gmask) >> 16;
Uint8 b = (pixelValue & bmask) >> 8;
Uint8 a = pixelValue & amask;
// invert colors ? tried the channels even separatet or in combination of 2 out
// r = 255 - r;
// g = 255 - g;
// b = 255 - b;
// new order of the color-channels
*pixel = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
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 > destination.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 };
// The pixeldata if Lock is possible
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() );
MessageBox( "In class TexturWrapper:" , 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 )
{
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 proportions
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() );
MessageBox( "In class TexturWrapper:" , txt );
exit( EXIT_FAILURE );
}
else
{
ImageBreite = b;
ImageHoehe = h;
}
Renderziel();
SDL_SetRenderDrawColor(myRenderer, 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(myRenderer);
RenderzielReset();
SDL_SetTextureBlendMode( ImageTextur , SDL_BLENDMODE_BLEND );
return ImageTextur != NULL;
}
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) { // copy sfc to result line by line, upside-down
memcpy(rpixels,pixels,pitch);
pixels -= pitch;
rpixels += pitch;
}
return result;
}
这是我的问题所在的方法:
// This is the method I try to get to work correctly!
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;
/* // To gether information about the SDL_RenderReadPixels output-format
char txt[ 256 ];
sprintf( txt , "\nBits per pixel: %d\n Bytes per pixel: %d\n Rmask: %08X\n Gmask: %08X\n Bmask: %08X\n Amask: %08X\n" , format->BitsPerPixel, format->BytesPerPixel, format->Rmask, format->Gmask, format->Bmask, format->Amask);
MessageBox( "Das Format:" , txt );
*/
// lock if needed
if (SDL_MUSTLOCK(surface)) {
SDL_LockSurface(surface);
}
// manipulate pixeldata
rearrangePixels((Uint8*)surface->pixels, surface->w, surface->h, format->Rmask, format->Gmask, format->Bmask, format->Amask);
// Unlock if needet
if (SDL_MUSTLOCK(surface)) {
SDL_UnlockSurface(surface);
}
SDL_Surface* flipSurf = flip_surface_vertical( surface ); // because RenderReadPixels returns upsidedown data
IMG_SavePNG(flipSurf, file_name);
SDL_FreeSurface(surface);
SDL_FreeSurface(flipSurf);
SDL_SetRenderTarget(myRenderer, NULL);
}
最后是 main() 来测试一切:
int main( void )
{
std::string picturefile = "resources/M.png"; // "path to an imagefile.png"
if ( !sdlInit( "Testing TexturWrapper class" ) ) { // Initialize SDL2 and SDL_Image
exit( EXIT_FAILURE );
}
// TexturWrapper class objects
TexturWrapper tw1;
TexturWrapper tw2;
TexturWrapper tw3;
tw1.Laden( picturefile ); // Load the imagefile without color-keying
SDL_Rect screen = GetDisplayMode();
tw1.Rendern( screen.x , screen.y , NULL , 0.0 , NULL , SDL_FLIP_NONE , &screen, false ); // Render it (scaled) to the middle of the screen... // false = renderpoint is not centered (0 = 0 and not -(half of the texture.w))
SDL_RenderPresent( myRenderer ); // Show rendered stuff on the screen
SDL_Delay( 3000 );
tw2.Blank( tw1.getRenderW() , tw1.getRenderH() , 't' ); // 't' = SDL_TEXTUREACCESS_TARGET
tw2.Renderziel(); // This object is now the rendertarget
tw1.Rendern( screen.x , screen.y , NULL , 0.0 , NULL , SDL_FLIP_NONE , &screen , false ); // 'copy' tw1 to tw2
tw2.RenderzielReset(); // Rendertarget = myWindow
tw2.Rendern( screen.x , screen.y , NULL , 0.0 , NULL , SDL_FLIP_NONE , &screen , false ); // show the copy
SDL_RenderPresent( myRenderer ); // Show rendered stuff on the screen
SDL_Delay( 3000 );
SDL_Rect clip = {0, 0, 500, 500}; // if you want to save just a clip of the copy
SDL_Rect rquad = {0, 0, tw2.getBreite()/4, tw2.getHoehe()/4}; // size of the picture.png
tw2.SaveImage( "Testfile.png" );//, &clip, &rquad ); // Save the copy as image
tw3.Laden( "Testfile.png" ); // Load the saved image
tw3.Rendern( screen.x , screen.y , NULL , 0.0 , NULL , SDL_FLIP_NONE , &screen, false ); // Show then loaded
SDL_RenderPresent( myRenderer ); // Show rendered stuff on the screen
SDL_Delay( 3000 );
// clean up
tw1.leeren();
tw2.leeren();
tw3.leeren();
sdlExit();
exit( EXIT_SUCCESS );
}
我也使用 sdl2 进行编程。我在 Windows 上成功测试了您的 SaveImage 方法作为纯函数,这意味着结果的颜色排列正确并且没有扭曲。因此,您关于 SDL_RenderReadPixels 由于硬件而提供不同回报的假设可能是正确的。
数据的测定给你揭示了什么?
/* // 收集有关 SDL_RenderReadPixels 输出格式的信息 字符txt[256]; sprintf( txt , " 每像素位数:%d 每像素字节数:%d 掩码:%08X 掩码:%08X B掩码:%08X 阿马斯克:%08X " , 格式->BitsPerPixel, 格式->BytesPerPixel, 格式->Rmask, 格式->Gmask, 格式->Bmask, 格式->Amask); MessageBox( "Das 格式:" , txt ); */
在我看来,你会在这里找到解决问题的关键。