Merge pull request #90 from gizmo98/patch-2
[libretro-ppsspp.git] / Core / Screenshot.cpp
blob6a9a446607aa33f8d1c4e6638023e021bf14b8a6
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/.
18 #ifdef USING_QT_UI
19 #include <QtGui/QImage>
20 #else
21 #include <libpng17/png.h>
22 #include "ext/jpge/jpge.h"
23 #endif
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"
30 #ifdef _WIN32
31 #include "GPU/Directx9/GPU_DX9.h"
32 #endif
33 #include "GPU/GLES/GLES_GPU.h"
34 #include "GPU/GPUInterface.h"
35 #include "GPU/GPUState.h"
37 #ifndef USING_QT_UI
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
42 public:
43 JPEGFileStream(const char *filename) {
44 fp_ = File::OpenCFile(filename, "wb");
46 ~JPEGFileStream() override {
47 if (fp_ ) {
48 fclose(fp_);
52 bool put_buf(const void *buf, int len) override
54 if (fp_) {
55 if (fwrite(buf, len, 1, fp_) != 1) {
56 fclose(fp_);
57 fp_ = nullptr;
60 return Valid();
63 bool Valid() {
64 return fp_ != nullptr;
67 private:
68 FILE *fp_;
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.");
75 return false;
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.");
81 return false;
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.");
89 return false;
92 if (!dst_image.process_scanline(NULL)) {
93 ERROR_LOG(COMMON, "Screenshot JPEG encode scanline flush failed.");
94 return false;
98 if (!dst_stream.Valid()) {
99 ERROR_LOG(COMMON, "Screenshot file write failed.");
102 dst_image.deinit();
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");
108 if (!fp) {
109 ERROR_LOG(COMMON, "Unable to open screenshot file for writing.");
110 return false;
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.");
116 return false;
118 return true;
119 } else {
120 ERROR_LOG(COMMON, "Screenshot PNG encode failed.");
121 fclose(fp);
122 remove(filename);
123 return false;
126 #endif
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.
130 temp = nullptr;
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);
139 buffer = temp;
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++) {
154 u8 *dst;
155 if (flip) {
156 dst = &temp[(buf.GetHeight() - y - 1) * buf.GetStride() * 3 + x * 3];
157 } else {
158 dst = &temp[y * buf.GetStride() * 3 + x * 3];
161 u8 &r = brswap ? dst[2] : dst[0];
162 u8 &g = dst[1];
163 u8 &b = brswap ? dst[0] : dst[2];
165 u32 src;
166 switch (baseFmt) {
167 case GPU_DBG_FORMAT_565:
168 src = buf16[y * buf.GetStride() + x];
169 if (rev) {
170 src = bswap16(src);
172 r = Convert5To8((src >> 0) & 0x1F);
173 g = Convert6To8((src >> 5) & 0x3F);
174 b = Convert5To8((src >> 11) & 0x1F);
175 break;
176 case GPU_DBG_FORMAT_5551:
177 src = buf16[y * buf.GetStride() + x];
178 if (rev) {
179 src = bswap16(src);
181 r = Convert5To8((src >> 0) & 0x1F);
182 g = Convert5To8((src >> 5) & 0x1F);
183 b = Convert5To8((src >> 10) & 0x1F);
184 break;
185 case GPU_DBG_FORMAT_4444:
186 src = buf16[y * buf.GetStride() + x];
187 if (rev) {
188 src = bswap16(src);
190 r = Convert4To8((src >> 0) & 0xF);
191 g = Convert4To8((src >> 4) & 0xF);
192 b = Convert4To8((src >> 8) & 0xF);
193 break;
194 case GPU_DBG_FORMAT_8888:
195 src = buf32[y * buf.GetStride() + x];
196 if (rev) {
197 src = bswap32(src);
199 r = (src >> 0) & 0xFF;
200 g = (src >> 8) & 0xFF;
201 b = (src >> 16) & 0xFF;
202 break;
203 default:
204 ERROR_LOG(COMMON, "Unsupported framebuffer format for screenshot: %d", buf.GetFormat());
205 return nullptr;
209 buffer = temp;
212 return buffer;
215 bool TakeGameScreenshot(const char *filename, ScreenshotFormat fmt, ScreenshotType type) {
216 GPUDebugBuffer buf;
217 bool success = false;
219 #if 0
220 if (type == SCREENSHOT_RENDER) {
221 if (gpuDebug) {
222 success = gpuDebug->GetCurrentFramebuffer(buf);
224 } else {
225 if (g_Config.iGPUBackend == GPU_BACKEND_OPENGL) {
226 success = GLES_GPU::GetDisplayFramebuffer(buf);
227 #ifdef _WIN32
228 } else if (g_Config.iGPUBackend == GPU_BACKEND_DIRECT3D9) {
229 success = DX9::DIRECTX9_GPU::GetDisplayFramebuffer(buf);
230 #endif
233 #endif
235 if (!success) {
236 ERROR_LOG(COMMON, "Failed to obtain screenshot data.");
237 return false;
240 #ifdef USING_QT_UI
241 if (success) {
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;
249 #else
250 if (success) {
251 u8 *flipbuffer = nullptr;
252 const u8 *buffer = ConvertBufferTo888RGB(buf, flipbuffer);
253 if (buffer == nullptr) {
254 success = false;
257 if (success && fmt == SCREENSHOT_PNG) {
258 png_image 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.");
269 success = false;
271 } else if (success && fmt == SCREENSHOT_JPG) {
272 jpge::params params;
273 params.m_quality = 90;
274 success = WriteScreenshotToJPEG(filename, buf.GetStride(), buf.GetHeight(), 3, flipbuffer, params);
275 } else {
276 success = false;
278 delete [] flipbuffer;
280 #endif
281 if (!success) {
282 ERROR_LOG(COMMON, "Failed to write screenshot.");
284 return success;