@@ -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"
@@ -3141,6 +3147,103 @@ void W3DDisplay::takeScreenShot(void)
31413147 TheInGameUI->message (TheGameText->fetch (" GUI:ScreenCapture" ), ufileName.str ());
31423148}
31433149
3150+ // / TheSuperHackers @bobtista 02/11/2025 Save compressed screenshot (JPEG/PNG) to file without stalling the game
3151+ // / This implementation captures the frame buffer on the main thread, then spawns a background thread
3152+ // / to compress and save the image, allowing the game to continue running smoothly.
3153+ void W3DDisplay::takeScreenShotCompressed (void )
3154+ {
3155+ // TheSuperHackers @bobtista 02/11/2025 Find next available filename
3156+ char leafname[256 ];
3157+ char pathname[1024 ];
3158+ static int frame_number = 1 ;
3159+
3160+ Bool done = false ;
3161+ while (!done) {
3162+ sprintf (leafname, " sshot%.3d.jpg" , frame_number++);
3163+ strcpy (pathname, TheGlobalData->getPath_UserData ().str ());
3164+ strlcat (pathname, leafname, ARRAY_SIZE (pathname));
3165+ if (_access (pathname, 0 ) == -1 )
3166+ done = true ;
3167+ }
3168+
3169+ // TheSuperHackers @bobtista 02/11/2025 Get the back buffer and create a copy
3170+ SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer ();
3171+ SurfaceClass::SurfaceDescription surfaceDesc;
3172+ surface->Get_Description (surfaceDesc);
3173+
3174+ SurfaceClass* surfaceCopy = NEW_REF (SurfaceClass, (DX8Wrapper::_Create_DX8_Surface (surfaceDesc.Width , surfaceDesc.Height , surfaceDesc.Format )));
3175+ DX8Wrapper::_Copy_DX8_Rects (surface->Peek_D3D_Surface (), NULL , 0 , surfaceCopy->Peek_D3D_Surface (), NULL );
3176+
3177+ surface->Release_Ref ();
3178+ surface = NULL ;
3179+
3180+ struct Rect
3181+ {
3182+ int Pitch;
3183+ void * pBits;
3184+ } lrect;
3185+
3186+ lrect.pBits = surfaceCopy->Lock (&lrect.Pitch );
3187+ if (lrect.pBits == NULL )
3188+ {
3189+ surfaceCopy->Release_Ref ();
3190+ return ;
3191+ }
3192+
3193+ unsigned int x, y, index, index2;
3194+ unsigned int width = surfaceDesc.Width ;
3195+ unsigned int height = surfaceDesc.Height ;
3196+
3197+ // TheSuperHackers @bobtista 02/11/2025 Allocate buffer for RGB image data
3198+ // Using shared_ptr for automatic cleanup in the background thread
3199+ std::shared_ptr<unsigned char > imageData (new unsigned char [3 * width * height],
3200+ std::default_delete<unsigned char []>());
3201+ unsigned char * image = imageData.get ();
3202+
3203+ // TheSuperHackers @bobtista 02/11/2025 Copy and convert BGRA to RGB
3204+ for (y = 0 ; y < height; y++)
3205+ {
3206+ for (x = 0 ; x < width; x++)
3207+ {
3208+ index = 3 * (x + y * width);
3209+ index2 = y * lrect.Pitch + 4 * x;
3210+
3211+ image[index] = *((unsigned char *)lrect.pBits + index2 + 2 ); // R
3212+ image[index + 1 ] = *((unsigned char *)lrect.pBits + index2 + 1 ); // G
3213+ image[index + 2 ] = *((unsigned char *)lrect.pBits + index2 + 0 ); // B
3214+ }
3215+ }
3216+
3217+ surfaceCopy->Unlock ();
3218+ surfaceCopy->Release_Ref ();
3219+ surfaceCopy = NULL ;
3220+
3221+ // TheSuperHackers @bobtista 02/11/2025 Make a copy of the pathname for the background thread
3222+ std::string pathnameCopy (pathname);
3223+ std::string leafnameCopy (leafname);
3224+
3225+ // TheSuperHackers @bobtista 02/11/2025 Spawn background thread to compress and save the image
3226+ // This allows the game to continue running without freezing
3227+ std::thread ([imageData, width, height, pathnameCopy, leafnameCopy]() {
3228+ // TheSuperHackers @bobtista 02/11/2025 Write JPEG with quality 90 (range: 1-100)
3229+ // stbi_write_jpg expects image data with Y-axis going down, which matches our data
3230+ int result = stbi_write_jpg (pathnameCopy.c_str (), width, height, 3 , imageData.get (), 90 );
3231+
3232+ if (!result) {
3233+ // TheSuperHackers @bobtista 02/11/2025 Log error if write failed
3234+ // Note: Can't show UI message from background thread
3235+ OutputDebugStringA (" Failed to write screenshot JPEG\n " );
3236+ }
3237+
3238+ // TheSuperHackers @bobtista 02/11/2025 imageData will be automatically cleaned up when shared_ptr goes out of scope
3239+ }).detach (); // TheSuperHackers @bobtista 02/11/2025 Detach thread to run independently
3240+
3241+ // TheSuperHackers @bobtista 02/11/2025 Show message to user immediately (file is being saved in background)
3242+ UnicodeString ufileName;
3243+ ufileName.translate (leafnameCopy.c_str ());
3244+ TheInGameUI->message (TheGameText->fetch (" GUI:ScreenCapture" ), ufileName.str ());
3245+ }
3246+
31443247/* * Start/Stop capturing an AVI movie*/
31453248void W3DDisplay::toggleMovieCapture (void )
31463249{
0 commit comments