1 // Copyright (c) 2012- PPSSPP Project.
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
19 #include <QtGui/QImage>
21 #include <libpng17/png.h>
22 #include "ext/jpge/jpge.h"
25 #include "Common/ColorConv.h"
26 #include "Common/FileUtil.h"
27 #include "Core/Config.h"
28 #include "Core/Screenshot.h"
29 #include "GPU/Common/GPUDebugInterface.h"
31 #include "GPU/Directx9/GPU_DX9.h"
33 #include "GPU/GLES/GLES_GPU.h"
34 #include "GPU/GPUInterface.h"
35 #include "GPU/GPUState.h"
38 // This is used to make non-ASCII paths work for filename.
39 // Technically only needed on Windows.
40 class JPEGFileStream
: public jpge::output_stream
43 JPEGFileStream(const char *filename
) {
44 fp_
= File::OpenCFile(filename
, "wb");
46 ~JPEGFileStream() override
{
52 bool put_buf(const void *buf
, int len
) override
55 if (fwrite(buf
, len
, 1, fp_
) != 1) {
64 return fp_
!= nullptr;
71 static bool WriteScreenshotToJPEG(const char *filename
, int width
, int height
, int num_channels
, const uint8
*image_data
, const jpge::params
&comp_params
) {
72 JPEGFileStream
dst_stream(filename
);
73 if (!dst_stream
.Valid()) {
74 ERROR_LOG(COMMON
, "Unable to open screenshot file for writing.");
78 jpge::jpeg_encoder dst_image
;
79 if (!dst_image
.init(&dst_stream
, width
, height
, num_channels
, comp_params
)) {
80 ERROR_LOG(COMMON
, "Screenshot JPEG encode init failed.");
84 for (u32 pass_index
= 0; pass_index
< dst_image
.get_total_passes(); pass_index
++) {
85 for (int i
= 0; i
< height
; i
++) {
86 const uint8
*buf
= image_data
+ i
* width
* num_channels
;
87 if (!dst_image
.process_scanline(buf
)) {
88 ERROR_LOG(COMMON
, "Screenshot JPEG encode scanline failed.");
92 if (!dst_image
.process_scanline(NULL
)) {
93 ERROR_LOG(COMMON
, "Screenshot JPEG encode scanline flush failed.");
98 if (!dst_stream
.Valid()) {
99 ERROR_LOG(COMMON
, "Screenshot file write failed.");
103 return dst_stream
.Valid();
106 static bool WriteScreenshotToPNG(png_imagep image
, const char *filename
, int convert_to_8bit
, const void *buffer
, png_int_32 row_stride
, const void *colormap
) {
107 FILE *fp
= File::OpenCFile(filename
, "wb");
109 ERROR_LOG(COMMON
, "Unable to open screenshot file for writing.");
113 if (png_image_write_to_stdio(image
, fp
, convert_to_8bit
, buffer
, row_stride
, colormap
)) {
114 if (fclose(fp
) != 0) {
115 ERROR_LOG(COMMON
, "Screenshot file write failed.");
120 ERROR_LOG(COMMON
, "Screenshot PNG encode failed.");
128 static const u8
*ConvertBufferTo888RGB(const GPUDebugBuffer
&buf
, u8
*&temp
) {
129 // The temp buffer will be freed by the caller if set, and can be the return value.
132 const u8
*buffer
= buf
.GetData();
133 if (buf
.GetFlipped() && buf
.GetFormat() == GPU_DBG_FORMAT_888_RGB
) {
134 // Silly OpenGL reads upside down, we flip to another buffer for simplicity.
135 temp
= new u8
[3 * buf
.GetStride() * buf
.GetHeight()];
136 for (u32 y
= 0; y
< buf
.GetHeight(); y
++) {
137 memcpy(temp
+ y
* buf
.GetStride() * 3, buffer
+ (buf
.GetHeight() - y
- 1) * buf
.GetStride() * 3, buf
.GetStride() * 3);
140 } else if (buf
.GetFormat() != GPU_DBG_FORMAT_888_RGB
) {
141 // Let's boil it down to how we need to interpret the bits.
142 int baseFmt
= buf
.GetFormat() & ~(GPU_DBG_FORMAT_REVERSE_FLAG
| GPU_DBG_FORMAT_BRSWAP_FLAG
);
143 bool rev
= (buf
.GetFormat() & GPU_DBG_FORMAT_REVERSE_FLAG
) != 0;
144 bool brswap
= (buf
.GetFormat() & GPU_DBG_FORMAT_BRSWAP_FLAG
) != 0;
145 bool flip
= buf
.GetFlipped();
147 temp
= new u8
[3 * buf
.GetStride() * buf
.GetHeight()];
149 // This is pretty inefficient.
150 const u16
*buf16
= (const u16
*)buffer
;
151 const u32
*buf32
= (const u32
*)buffer
;
152 for (u32 y
= 0; y
< buf
.GetHeight(); y
++) {
153 for (u32 x
= 0; x
< buf
.GetStride(); x
++) {
156 dst
= &temp
[(buf
.GetHeight() - y
- 1) * buf
.GetStride() * 3 + x
* 3];
158 dst
= &temp
[y
* buf
.GetStride() * 3 + x
* 3];
161 u8
&r
= brswap
? dst
[2] : dst
[0];
163 u8
&b
= brswap
? dst
[0] : dst
[2];
167 case GPU_DBG_FORMAT_565
:
168 src
= buf16
[y
* buf
.GetStride() + x
];
172 r
= Convert5To8((src
>> 0) & 0x1F);
173 g
= Convert6To8((src
>> 5) & 0x3F);
174 b
= Convert5To8((src
>> 11) & 0x1F);
176 case GPU_DBG_FORMAT_5551
:
177 src
= buf16
[y
* buf
.GetStride() + x
];
181 r
= Convert5To8((src
>> 0) & 0x1F);
182 g
= Convert5To8((src
>> 5) & 0x1F);
183 b
= Convert5To8((src
>> 10) & 0x1F);
185 case GPU_DBG_FORMAT_4444
:
186 src
= buf16
[y
* buf
.GetStride() + x
];
190 r
= Convert4To8((src
>> 0) & 0xF);
191 g
= Convert4To8((src
>> 4) & 0xF);
192 b
= Convert4To8((src
>> 8) & 0xF);
194 case GPU_DBG_FORMAT_8888
:
195 src
= buf32
[y
* buf
.GetStride() + x
];
199 r
= (src
>> 0) & 0xFF;
200 g
= (src
>> 8) & 0xFF;
201 b
= (src
>> 16) & 0xFF;
204 ERROR_LOG(COMMON
, "Unsupported framebuffer format for screenshot: %d", buf
.GetFormat());
215 bool TakeGameScreenshot(const char *filename
, ScreenshotFormat fmt
, ScreenshotType type
) {
217 bool success
= false;
220 if (type
== SCREENSHOT_RENDER
) {
222 success
= gpuDebug
->GetCurrentFramebuffer(buf
);
225 if (g_Config
.iGPUBackend
== GPU_BACKEND_OPENGL
) {
226 success
= GLES_GPU::GetDisplayFramebuffer(buf
);
228 } else if (g_Config
.iGPUBackend
== GPU_BACKEND_DIRECT3D9
) {
229 success
= DX9::DIRECTX9_GPU::GetDisplayFramebuffer(buf
);
236 ERROR_LOG(COMMON
, "Failed to obtain screenshot data.");
242 u8
*flipbuffer
= nullptr;
243 const u8
*buffer
= ConvertBufferTo888RGB(buf
, flipbuffer
);
244 // TODO: Handle other formats (e.g. Direct3D, raw framebuffers.)
245 QImage
image(buffer
, buf
.GetStride(), buf
.GetHeight(), QImage::Format_RGB888
);
246 success
= image
.save(filename
, fmt
== SCREENSHOT_PNG
? "PNG" : "JPG");
247 delete [] flipbuffer
;
251 u8
*flipbuffer
= nullptr;
252 const u8
*buffer
= ConvertBufferTo888RGB(buf
, flipbuffer
);
253 if (buffer
== nullptr) {
257 if (success
&& fmt
== SCREENSHOT_PNG
) {
259 memset(&png
, 0, sizeof(png
));
260 png
.version
= PNG_IMAGE_VERSION
;
261 png
.format
= PNG_FORMAT_RGB
;
262 png
.width
= buf
.GetStride();
263 png
.height
= buf
.GetHeight();
264 success
= WriteScreenshotToPNG(&png
, filename
, 0, flipbuffer
, buf
.GetStride() * 3, nullptr);
265 png_image_free(&png
);
267 if (png
.warning_or_error
>= 2) {
268 ERROR_LOG(COMMON
, "Saving screenshot to PNG produced errors.");
271 } else if (success
&& fmt
== SCREENSHOT_JPG
) {
273 params
.m_quality
= 90;
274 success
= WriteScreenshotToJPEG(filename
, buf
.GetStride(), buf
.GetHeight(), 3, flipbuffer
, params
);
278 delete [] flipbuffer
;
282 ERROR_LOG(COMMON
, "Failed to write screenshot.");