Skip to content

Commit 953d6d4

Browse files
committed
Move screenshot logic to Core to eliminate code duplication
Add shared screenshot implementation in Core
1 parent 9206af3 commit 953d6d4

File tree

5 files changed

+187
-206
lines changed

5 files changed

+187
-206
lines changed

Core/GameEngineDevice/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ set(GAMEENGINEDEVICE_SRC
7171
# Include/W3DDevice/GameClient/W3DTerrainVisual.h
7272
# Include/W3DDevice/GameClient/W3DTreeBuffer.h
7373
Include/W3DDevice/GameClient/W3DVideoBuffer.h
74+
Include/W3DDevice/GameClient/W3DScreenshot.h
7475
# Include/W3DDevice/GameClient/W3DView.h
7576
# Include/W3DDevice/GameClient/W3DVolumetricShadow.h
7677
# Include/W3DDevice/GameClient/W3DWater.h
@@ -173,6 +174,7 @@ set(GAMEENGINEDEVICE_SRC
173174
# Source/W3DDevice/GameClient/W3DTerrainVisual.cpp
174175
# Source/W3DDevice/GameClient/W3DTreeBuffer.cpp
175176
Source/W3DDevice/GameClient/W3DVideoBuffer.cpp
177+
Source/W3DDevice/GameClient/W3DScreenshot.cpp
176178
# Source/W3DDevice/GameClient/W3DView.cpp
177179
# Source/W3DDevice/GameClient/W3dWaypointBuffer.cpp
178180
# Source/W3DDevice/GameClient/W3DWebBrowser.cpp
@@ -220,6 +222,7 @@ target_include_directories(corei_gameenginedevice_public INTERFACE
220222
target_link_libraries(corei_gameenginedevice_private INTERFACE
221223
corei_always
222224
corei_main
225+
stb
223226
)
224227

225228
target_link_libraries(corei_gameenginedevice_public INTERFACE
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
enum ScreenshotFormat
22+
{
23+
SCREENSHOT_JPEG,
24+
SCREENSHOT_PNG
25+
};
26+
27+
void W3D_TakeCompressedScreenshot(ScreenshotFormat format, int quality = 80);
28+
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include <stdlib.h>
20+
#include <windows.h>
21+
#include <io.h>
22+
23+
#define STB_IMAGE_WRITE_IMPLEMENTATION
24+
#include <stb_image_write.h>
25+
26+
#include "W3DDevice/GameClient/W3DScreenshot.h"
27+
#include "Common/GlobalData.h"
28+
#include "GameClient/InGameUI.h"
29+
#include "GameClient/GameText.h"
30+
#include "WW3D2/dx8wrapper.h"
31+
#include "WW3D2/surface.h"
32+
33+
struct ScreenshotThreadData
34+
{
35+
unsigned char* imageData;
36+
unsigned int width;
37+
unsigned int height;
38+
char pathname[1024];
39+
char leafname[256];
40+
int quality;
41+
ScreenshotFormat format;
42+
};
43+
44+
static DWORD WINAPI screenshotThreadFunc(LPVOID param)
45+
{
46+
ScreenshotThreadData* data = (ScreenshotThreadData*)param;
47+
48+
int result = 0;
49+
if (data->format == SCREENSHOT_JPEG)
50+
{
51+
result = stbi_write_jpg(data->pathname, data->width, data->height, 3, data->imageData, data->quality);
52+
}
53+
else if (data->format == SCREENSHOT_PNG)
54+
{
55+
result = stbi_write_png(data->pathname, data->width, data->height, 3, data->imageData, data->width * 3);
56+
}
57+
58+
if (!result) {
59+
OutputDebugStringA("Failed to write screenshot\n");
60+
}
61+
62+
delete [] data->imageData;
63+
delete data;
64+
65+
return 0;
66+
}
67+
68+
void W3D_TakeCompressedScreenshot(ScreenshotFormat format, int quality)
69+
{
70+
char leafname[256];
71+
char pathname[1024];
72+
static int jpegFrameNumber = 1;
73+
static int pngFrameNumber = 1;
74+
75+
int* frameNumber = (format == SCREENSHOT_JPEG) ? &jpegFrameNumber : &pngFrameNumber;
76+
const char* extension = (format == SCREENSHOT_JPEG) ? "jpg" : "png";
77+
78+
Bool done = false;
79+
while (!done) {
80+
sprintf(leafname, "sshot%.3d.%s", (*frameNumber)++, extension);
81+
strcpy(pathname, TheGlobalData->getPath_UserData().str());
82+
strlcat(pathname, leafname, ARRAY_SIZE(pathname));
83+
if (_access(pathname, 0) == -1)
84+
done = true;
85+
}
86+
87+
SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer();
88+
SurfaceClass::SurfaceDescription surfaceDesc;
89+
surface->Get_Description(surfaceDesc);
90+
91+
SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)));
92+
DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), NULL, 0, surfaceCopy->Peek_D3D_Surface(), NULL);
93+
94+
surface->Release_Ref();
95+
surface = NULL;
96+
97+
struct Rect
98+
{
99+
int Pitch;
100+
void* pBits;
101+
} lrect;
102+
103+
lrect.pBits = surfaceCopy->Lock(&lrect.Pitch);
104+
if (lrect.pBits == NULL)
105+
{
106+
surfaceCopy->Release_Ref();
107+
return;
108+
}
109+
110+
unsigned int x, y, index, index2;
111+
unsigned int width = surfaceDesc.Width;
112+
unsigned int height = surfaceDesc.Height;
113+
114+
unsigned char* image = new unsigned char[3 * width * height];
115+
116+
for (y = 0; y < height; y++)
117+
{
118+
for (x = 0; x < width; x++)
119+
{
120+
index = 3 * (x + y * width);
121+
index2 = y * lrect.Pitch + 4 * x;
122+
123+
image[index] = *((unsigned char*)lrect.pBits + index2 + 2);
124+
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1);
125+
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0);
126+
}
127+
}
128+
129+
surfaceCopy->Unlock();
130+
surfaceCopy->Release_Ref();
131+
surfaceCopy = NULL;
132+
133+
ScreenshotThreadData* threadData = new ScreenshotThreadData();
134+
threadData->imageData = image;
135+
threadData->width = width;
136+
threadData->height = height;
137+
threadData->quality = quality;
138+
threadData->format = format;
139+
strcpy(threadData->pathname, pathname);
140+
strcpy(threadData->leafname, leafname);
141+
142+
DWORD threadId;
143+
HANDLE hThread = CreateThread(NULL, 0, screenshotThreadFunc, threadData, 0, &threadId);
144+
if (hThread) {
145+
CloseHandle(hThread);
146+
}
147+
148+
UnicodeString ufileName;
149+
ufileName.translate(leafname);
150+
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
151+
}
152+

Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,8 @@ static void drawFramerateBar(void);
4040
#include <io.h>
4141
#include <time.h>
4242

43-
#define STB_IMAGE_WRITE_IMPLEMENTATION
44-
#include <stb_image_write.h>
45-
4643
// USER INCLUDES //////////////////////////////////////////////////////////////
44+
#include "W3DDevice/GameClient/W3DScreenshot.h"
4745
#include "Common/FramePacer.h"
4846
#include "Common/ThingFactory.h"
4947
#include "Common/GlobalData.h"
@@ -3019,108 +3017,9 @@ void W3DDisplay::takeScreenShot(void)
30193017
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
30203018
}
30213019

3022-
struct ScreenshotThreadData
3023-
{
3024-
unsigned char* imageData;
3025-
unsigned int width;
3026-
unsigned int height;
3027-
char pathname[1024];
3028-
char leafname[256];
3029-
};
3030-
3031-
static DWORD WINAPI screenshotThreadFunc(LPVOID param)
3032-
{
3033-
ScreenshotThreadData* data = (ScreenshotThreadData*)param;
3034-
3035-
int result = stbi_write_jpg(data->pathname, data->width, data->height, 3, data->imageData, 90);
3036-
3037-
if (!result) {
3038-
OutputDebugStringA("Failed to write screenshot JPEG\n");
3039-
}
3040-
3041-
delete [] data->imageData;
3042-
delete data;
3043-
3044-
return 0;
3045-
}
3046-
30473020
void W3DDisplay::takeScreenShotCompressed(void)
30483021
{
3049-
char leafname[256];
3050-
char pathname[1024];
3051-
static int frame_number = 1;
3052-
3053-
Bool done = false;
3054-
while (!done) {
3055-
sprintf(leafname, "sshot%.3d.jpg", frame_number++);
3056-
strcpy(pathname, TheGlobalData->getPath_UserData().str());
3057-
strlcat(pathname, leafname, ARRAY_SIZE(pathname));
3058-
if (_access(pathname, 0) == -1)
3059-
done = true;
3060-
}
3061-
3062-
SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer();
3063-
SurfaceClass::SurfaceDescription surfaceDesc;
3064-
surface->Get_Description(surfaceDesc);
3065-
3066-
SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)));
3067-
DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), NULL, 0, surfaceCopy->Peek_D3D_Surface(), NULL);
3068-
3069-
surface->Release_Ref();
3070-
surface = NULL;
3071-
3072-
struct Rect
3073-
{
3074-
int Pitch;
3075-
void* pBits;
3076-
} lrect;
3077-
3078-
lrect.pBits = surfaceCopy->Lock(&lrect.Pitch);
3079-
if (lrect.pBits == NULL)
3080-
{
3081-
surfaceCopy->Release_Ref();
3082-
return;
3083-
}
3084-
3085-
unsigned int x, y, index, index2;
3086-
unsigned int width = surfaceDesc.Width;
3087-
unsigned int height = surfaceDesc.Height;
3088-
3089-
unsigned char* image = new unsigned char[3 * width * height];
3090-
3091-
for (y = 0; y < height; y++)
3092-
{
3093-
for (x = 0; x < width; x++)
3094-
{
3095-
index = 3 * (x + y * width);
3096-
index2 = y * lrect.Pitch + 4 * x;
3097-
3098-
image[index] = *((unsigned char*)lrect.pBits + index2 + 2);
3099-
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1);
3100-
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0);
3101-
}
3102-
}
3103-
3104-
surfaceCopy->Unlock();
3105-
surfaceCopy->Release_Ref();
3106-
surfaceCopy = NULL;
3107-
3108-
ScreenshotThreadData* threadData = new ScreenshotThreadData();
3109-
threadData->imageData = image;
3110-
threadData->width = width;
3111-
threadData->height = height;
3112-
strcpy(threadData->pathname, pathname);
3113-
strcpy(threadData->leafname, leafname);
3114-
3115-
DWORD threadId;
3116-
HANDLE hThread = CreateThread(NULL, 0, screenshotThreadFunc, threadData, 0, &threadId);
3117-
if (hThread) {
3118-
CloseHandle(hThread);
3119-
}
3120-
3121-
UnicodeString ufileName;
3122-
ufileName.translate(leafname);
3123-
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
3022+
W3D_TakeCompressedScreenshot(SCREENSHOT_JPEG, 80);
31243023
}
31253024

31263025
/** Start/Stop capturing an AVI movie*/

0 commit comments

Comments
 (0)