@@ -39,6 +39,12 @@ static void drawFramerateBar(void);
3939#include < windows.h>
4040#include < io.h>
4141#include < time.h>
42+ #include < thread>
43+ #include < memory>
44+
45+ // TheSuperHackers @bobtista 02/11/2025 STB for image encoding
46+ #define STB_IMAGE_WRITE_IMPLEMENTATION
47+ #include < stb_image_write.h>
4248
4349// USER INCLUDES //////////////////////////////////////////////////////////////
4450#include " Common/FramePacer.h"
@@ -3022,6 +3028,103 @@ void W3DDisplay::takeScreenShot(void)
30223028 TheInGameUI->message (TheGameText->fetch (" GUI:ScreenCapture" ), ufileName.str ());
30233029}
30243030
3031+ // / TheSuperHackers @bobtista 02/11/2025 Save compressed screenshot (JPEG/PNG) to file without stalling the game
3032+ // / This implementation captures the frame buffer on the main thread, then spawns a background thread
3033+ // / to compress and save the image, allowing the game to continue running smoothly.
3034+ void W3DDisplay::takeScreenShotCompressed (void )
3035+ {
3036+ // TheSuperHackers @bobtista 02/11/2025 Find next available filename
3037+ char leafname[256 ];
3038+ char pathname[1024 ];
3039+ static int frame_number = 1 ;
3040+
3041+ Bool done = false ;
3042+ while (!done) {
3043+ sprintf (leafname, " sshot%.3d.jpg" , frame_number++);
3044+ strcpy (pathname, TheGlobalData->getPath_UserData ().str ());
3045+ strlcat (pathname, leafname, ARRAY_SIZE (pathname));
3046+ if (_access (pathname, 0 ) == -1 )
3047+ done = true ;
3048+ }
3049+
3050+ // TheSuperHackers @bobtista 02/11/2025 Get the back buffer and create a copy
3051+ SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer ();
3052+ SurfaceClass::SurfaceDescription surfaceDesc;
3053+ surface->Get_Description (surfaceDesc);
3054+
3055+ SurfaceClass* surfaceCopy = NEW_REF (SurfaceClass, (DX8Wrapper::_Create_DX8_Surface (surfaceDesc.Width , surfaceDesc.Height , surfaceDesc.Format )));
3056+ DX8Wrapper::_Copy_DX8_Rects (surface->Peek_D3D_Surface (), NULL , 0 , surfaceCopy->Peek_D3D_Surface (), NULL );
3057+
3058+ surface->Release_Ref ();
3059+ surface = NULL ;
3060+
3061+ struct Rect
3062+ {
3063+ int Pitch;
3064+ void * pBits;
3065+ } lrect;
3066+
3067+ lrect.pBits = surfaceCopy->Lock (&lrect.Pitch );
3068+ if (lrect.pBits == NULL )
3069+ {
3070+ surfaceCopy->Release_Ref ();
3071+ return ;
3072+ }
3073+
3074+ unsigned int x, y, index, index2;
3075+ unsigned int width = surfaceDesc.Width ;
3076+ unsigned int height = surfaceDesc.Height ;
3077+
3078+ // TheSuperHackers @bobtista 02/11/2025 Allocate buffer for RGB image data
3079+ // Using shared_ptr for automatic cleanup in the background thread
3080+ std::shared_ptr<unsigned char > imageData (new unsigned char [3 * width * height],
3081+ std::default_delete<unsigned char []>());
3082+ unsigned char * image = imageData.get ();
3083+
3084+ // TheSuperHackers @bobtista 02/11/2025 Copy and convert BGRA to RGB
3085+ for (y = 0 ; y < height; y++)
3086+ {
3087+ for (x = 0 ; x < width; x++)
3088+ {
3089+ index = 3 * (x + y * width);
3090+ index2 = y * lrect.Pitch + 4 * x;
3091+
3092+ image[index] = *((unsigned char *)lrect.pBits + index2 + 2 ); // R
3093+ image[index + 1 ] = *((unsigned char *)lrect.pBits + index2 + 1 ); // G
3094+ image[index + 2 ] = *((unsigned char *)lrect.pBits + index2 + 0 ); // B
3095+ }
3096+ }
3097+
3098+ surfaceCopy->Unlock ();
3099+ surfaceCopy->Release_Ref ();
3100+ surfaceCopy = NULL ;
3101+
3102+ // TheSuperHackers @bobtista 02/11/2025 Make a copy of the pathname for the background thread
3103+ std::string pathnameCopy (pathname);
3104+ std::string leafnameCopy (leafname);
3105+
3106+ // TheSuperHackers @bobtista 02/11/2025 Spawn background thread to compress and save the image
3107+ // This allows the game to continue running without freezing
3108+ std::thread ([imageData, width, height, pathnameCopy, leafnameCopy]() {
3109+ // TheSuperHackers @bobtista 02/11/2025 Write JPEG with quality 90 (range: 1-100)
3110+ // stbi_write_jpg expects image data with Y-axis going down, which matches our data
3111+ int result = stbi_write_jpg (pathnameCopy.c_str (), width, height, 3 , imageData.get (), 90 );
3112+
3113+ if (!result) {
3114+ // TheSuperHackers @bobtista 02/11/2025 Log error if write failed
3115+ // Note: Can't show UI message from background thread
3116+ OutputDebugStringA (" Failed to write screenshot JPEG\n " );
3117+ }
3118+
3119+ // TheSuperHackers @bobtista 02/11/2025 imageData will be automatically cleaned up when shared_ptr goes out of scope
3120+ }).detach (); // TheSuperHackers @bobtista 02/11/2025 Detach thread to run independently
3121+
3122+ // TheSuperHackers @bobtista 02/11/2025 Show message to user immediately (file is being saved in background)
3123+ UnicodeString ufileName;
3124+ ufileName.translate (leafnameCopy.c_str ());
3125+ TheInGameUI->message (TheGameText->fetch (" GUI:ScreenCapture" ), ufileName.str ());
3126+ }
3127+
30253128/* * Start/Stop capturing an AVI movie*/
30263129void W3DDisplay::toggleMovieCapture (void )
30273130{
0 commit comments