1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus - screenshot.c *
3 * Mupen64Plus homepage: https://mupen64plus.org/ *
4 * Copyright (C) 2008 Richard42 *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22#include <SDL.h>
23#include <ctype.h>
24#include <png.h>
25#include <setjmp.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30
31#define M64P_CORE_PROTOTYPES 1
32#include "api/callbacks.h"
33#include "api/m64p_config.h"
34#include "api/m64p_types.h"
35#include "main/main.h"
36#include "main/rom.h"
37#include "main/util.h"
38#include "osal/files.h"
39#include "osal/preproc.h"
40#include "osd/osd.h"
41#include "plugin/plugin.h"
42
43/*********************************************************************************************************
44* PNG support functions for writing screenshot files
45*/
46
47static void mupen_png_error(png_structp png_write, const char *message)
48{
49 DebugMessage(M64MSG_ERROR, "PNG Error: %s", message);
50}
51
52static void mupen_png_warn(png_structp png_write, const char *message)
53{
54 DebugMessage(M64MSG_WARNING, "PNG Warning: %s", message);
55}
56
57static void user_write_data(png_structp png_write, png_bytep data, png_size_t length)
58{
59 FILE *fPtr = (FILE *) png_get_io_ptr(png_write);
60 if (fwrite(data, 1, length, fPtr) != length)
61 DebugMessage(M64MSG_ERROR, "Failed to write %zi bytes to screenshot file.", length);
62}
63
64static void user_flush_data(png_structp png_write)
65{
66 FILE *fPtr = (FILE *) png_get_io_ptr(png_write);
67 fflush(fPtr);
68}
69
70/*********************************************************************************************************
71* Other Local (static) functions
72*/
73
74static int SaveRGBBufferToFile(const char *filename, const unsigned char *buf, int width, int height, int pitch)
75{
76 int i;
77
78 // allocate PNG structures
79 png_structp png_write = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, mupen_png_error, mupen_png_warn);
80 if (!png_write)
81 {
82 DebugMessage(M64MSG_ERROR, "Error creating PNG write struct.");
83 return 1;
84 }
85 png_infop png_info = png_create_info_struct(png_write);
86 if (!png_info)
87 {
88 png_destroy_write_struct(&png_write, (png_infopp)NULL);
89 DebugMessage(M64MSG_ERROR, "Error creating PNG info struct.");
90 return 2;
91 }
92 // Set the jumpback
93 if (setjmp(png_jmpbuf(png_write)))
94 {
95 png_destroy_write_struct(&png_write, &png_info);
96 DebugMessage(M64MSG_ERROR, "Error calling setjmp()");
97 return 3;
98 }
99 // open the file to write
100 FILE *savefile = fopen(filename, "wb");
101 if (savefile == NULL)
102 {
103 DebugMessage(M64MSG_ERROR, "Error opening '%s' to save screenshot.", filename);
104 return 4;
105 }
106 // set function pointers in the PNG library, for write callbacks
107 png_set_write_fn(png_write, (png_voidp) savefile, user_write_data, user_flush_data);
108 // set the info
109 png_set_IHDR(png_write, png_info, width, height, 8, PNG_COLOR_TYPE_RGB,
110 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
111 // allocate row pointers and scale each row to 24-bit color
112 png_byte **row_pointers;
113 row_pointers = (png_byte **) malloc(height * sizeof(png_bytep));
114 for (i = 0; i < height; i++)
115 {
116 row_pointers[i] = (png_byte *) (buf + (height - 1 - i) * pitch);
117 }
118 // set the row pointers
119 png_set_rows(png_write, png_info, row_pointers);
120 // write the picture to disk
121 png_write_png(png_write, png_info, 0, NULL);
122 // free memory
123 free(row_pointers);
124 png_destroy_write_struct(&png_write, &png_info);
125 // close file
126 fclose(savefile);
127 // all done
128 return 0;
129}
130
131static int CurrentShotIndex;
132
133static char *GetNextScreenshotPath(void)
134{
135 char *ScreenshotPath;
136 char ScreenshotFileName[20 + 8 + 1];
137 char *pch;
138
139 // generate the base name of the screenshot
140 // add the ROM name, convert to lowercase, convert spaces to underscores
141 strcpy(ScreenshotFileName, ROM_PARAMS.headername);
142 for (pch = ScreenshotFileName; *pch != '\0'; pch++)
143 *pch = ((*pch == ' ') || (*pch == ':')) ? '_' : tolower(*pch);
144 strcat(ScreenshotFileName, "-###.png");
145
146 // add the base path to the screenshot file name
147 const char *SshotDir = ConfigGetParamString(g_CoreConfig, "ScreenshotPath");
148 if (SshotDir == NULL || *SshotDir == '\0')
149 {
150 // note the trick to avoid an allocation. we add a NUL character
151 // instead of the separator, call mkdir, then add the separator
152 ScreenshotPath = formatstr("%sscreenshot%c%s", ConfigGetUserDataPath(), '\0', ScreenshotFileName);
153 if (ScreenshotPath == NULL)
154 return NULL;
155 osal_mkdirp(ScreenshotPath, 0700);
156 ScreenshotPath[strlen(ScreenshotPath)] = OSAL_DIR_SEPARATORS[0];
157 }
158 else
159 {
160 ScreenshotPath = combinepath(SshotDir, ScreenshotFileName);
161 if (ScreenshotPath == NULL)
162 return NULL;
163 }
164
165 // patch the number part of the name (the '###' part) until we find a free spot
166 char *NumberPtr = ScreenshotPath + strlen(ScreenshotPath) - 7;
167 for (; CurrentShotIndex < 1000; CurrentShotIndex++)
168 {
169 sprintf(NumberPtr, "%03i.png", CurrentShotIndex);
170 FILE *pFile = fopen(ScreenshotPath, "r");
171 if (pFile == NULL)
172 break;
173 fclose(pFile);
174 }
175
176 if (CurrentShotIndex >= 1000)
177 {
178 DebugMessage(M64MSG_ERROR, "Can't save screenshot; folder already contains 1000 screenshots for this ROM");
179 free(ScreenshotPath);
180 return NULL;
181 }
182 CurrentShotIndex++;
183
184 return ScreenshotPath;
185}
186
187/*********************************************************************************************************
188* Global screenshot functions
189*/
190
191void ScreenshotRomOpen(void)
192{
193 CurrentShotIndex = 0;
194}
195
196void TakeScreenshot(int iFrameNumber)
197{
198 char *filename;
199
200 // look for an unused screenshot filename
201 filename = GetNextScreenshotPath();
202 if (filename == NULL)
203 return;
204
205 // get the width and height
206 int width = 640;
207 int height = 480;
208 gfx.readScreen(NULL, &width, &height, 0);
209
210 // allocate memory for the image
211 unsigned char *pucFrame = (unsigned char *) malloc(width * height * 3);
212 if (pucFrame == NULL)
213 {
214 free(filename);
215 return;
216 }
217
218 // grab the back image from OpenGL by calling the video plugin
219 gfx.readScreen(pucFrame, &width, &height, 0);
220
221 // write the image to a PNG
222 SaveRGBBufferToFile(filename, pucFrame, width, height, width * 3);
223 // free the memory
224 free(pucFrame);
225 free(filename);
226 // print message -- this allows developers to capture frames and use them in the regression test
227 main_message(M64MSG_INFO, OSD_BOTTOM_LEFT, "Captured screenshot for frame %i.", iFrameNumber);
228}
229
230