/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2016 - Daniel De Matteis * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #ifdef _XBOX1 #include #include #endif #include #include #include #include #include #include #include #include #include #if defined(HAVE_ZLIB_DEFLATE) && defined(HAVE_RPNG) #include #define IMG_EXT "png" #else #define IMG_EXT "bmp" #endif #include "general.h" #include "msg_hash.h" #include "retroarch.h" #include "screenshot.h" #include "verbosity.h" #include "gfx/video_driver.h" #include "gfx/scaler/scaler.h" #include "gfx/video_frame.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif /* Take frame bottom-up. */ static bool screenshot_dump(const char *folder, const void *frame, unsigned width, unsigned height, int pitch, bool bgr24) { bool ret; char filename[PATH_MAX_LENGTH]; char shotname[256]; settings_t *settings = config_get_ptr(); global_t *global = global_get_ptr(); #if defined(HAVE_ZLIB_DEFLATE) && defined(HAVE_RPNG) uint8_t *out_buffer = NULL; struct scaler_ctx scaler = {0}; #endif if (settings->auto_screenshot_filename) { fill_dated_filename(shotname, IMG_EXT, sizeof(shotname)); fill_pathname_join(filename, folder, shotname, sizeof(filename)); } else { snprintf(shotname, sizeof(shotname),"%s.png", path_basename(global->name.base)); fill_pathname_join(filename, folder, shotname, sizeof(filename)); } #ifdef _XBOX1 d3d_video_t *d3d = (d3d_video_t*)video_driver_get_ptr(true); D3DSurface *surf = NULL; d3d->dev->GetBackBuffer(-1, D3DBACKBUFFER_TYPE_MONO, &surf); ret = XGWriteSurfaceToFile(surf, filename); surf->Release(); if(ret == S_OK) ret = true; else ret = false; #elif defined(HAVE_ZLIB_DEFLATE) && defined(HAVE_RPNG) out_buffer = (uint8_t*)malloc(width * height * 3); if (!out_buffer) return false; video_frame_convert_to_bgr24( &scaler, out_buffer, (const uint8_t*)frame + ((int)height - 1) * pitch, width, height, -pitch, bgr24); scaler_ctx_gen_reset(&scaler); RARCH_LOG("Using RPNG for PNG screenshots.\n"); ret = rpng_save_image_bgr24( filename, out_buffer, width, height, width * 3 ); free(out_buffer); #else enum rbmp_source_type bmp_type = RBMP_SOURCE_TYPE_DONT_CARE; if (bgr24) bmp_type = RBMP_SOURCE_TYPE_BGR24; else if (video_driver_get_pixel_format() == RETRO_PIXEL_FORMAT_XRGB8888) bmp_type = RBMP_SOURCE_TYPE_XRGB888; ret = rbmp_save_image(filename, frame, width, height, pitch, bmp_type); #endif if (!ret) RARCH_ERR("Failed to take screenshot.\n"); return ret; } static bool take_screenshot_viewport(void) { char screenshot_path[PATH_MAX_LENGTH] = {0}; const char *screenshot_dir = NULL; uint8_t *buffer = NULL; bool retval = false; struct video_viewport vp = {0}; settings_t *settings = config_get_ptr(); global_t *global = global_get_ptr(); video_driver_get_viewport_info(&vp); if (!vp.width || !vp.height) return false; if (!(buffer = (uint8_t*)malloc(vp.width * vp.height * 3))) return false; if (!video_driver_read_viewport(buffer)) goto done; screenshot_dir = settings->directory.screenshot; if (!*settings->directory.screenshot) { fill_pathname_basedir(screenshot_path, global->name.base, sizeof(screenshot_path)); screenshot_dir = screenshot_path; } /* Data read from viewport is in bottom-up order, suitable for BMP. */ if (!screenshot_dump(screenshot_dir, buffer, vp.width, vp.height, vp.width * 3, true)) goto done; retval = true; done: if (buffer) free(buffer); return retval; } static bool take_screenshot_raw(void) { unsigned width, height; size_t pitch; char screenshot_path[PATH_MAX_LENGTH] = {0}; const void *data = NULL; global_t *global = global_get_ptr(); settings_t *settings = config_get_ptr(); const char *screenshot_dir = settings->directory.screenshot; video_driver_cached_frame_get(&data, &width, &height, &pitch); if (!*settings->directory.screenshot) { fill_pathname_basedir(screenshot_path, global->name.base, sizeof(screenshot_path)); screenshot_dir = screenshot_path; } /* Negative pitch is needed as screenshot takes bottom-up, * but we use top-down. */ return screenshot_dump(screenshot_dir, (const uint8_t*)data + (height - 1) * pitch, width, height, -pitch, false); } static bool take_screenshot_choice(void) { global_t *global = global_get_ptr(); settings_t *settings = config_get_ptr(); /* No way to infer screenshot directory. */ if ((!*settings->directory.screenshot) && (!*global->name.base)) return false; if (video_driver_supports_viewport_read()) { /* Avoid taking screenshot of GUI overlays. */ video_driver_set_texture_enable(false, false); video_driver_cached_frame_render(); return take_screenshot_viewport(); } if (!video_driver_cached_frame_has_valid_framebuffer()) return take_screenshot_raw(); if (video_driver_supports_read_frame_raw()) { unsigned old_width, old_height; size_t old_pitch; void *frame_data; bool ret = false; const void* old_data = NULL; video_driver_cached_frame_get(&old_data, &old_width, &old_height, &old_pitch); frame_data = video_driver_read_frame_raw( &old_width, &old_height, &old_pitch); video_driver_cached_frame_set(old_data, old_width, old_height, old_pitch); if (frame_data) { video_driver_set_cached_frame_ptr(frame_data); if (take_screenshot_raw()) ret = true; free(frame_data); } return ret; } return false; } /** * take_screenshot: * * Returns: true (1) if successful, otherwise false (0). **/ bool take_screenshot(void) { bool ret = take_screenshot_choice(); const char *msg_screenshot = ret ? msg_hash_to_str(MSG_TAKING_SCREENSHOT) : msg_hash_to_str(MSG_FAILED_TO_TAKE_SCREENSHOT); const char *msg = msg_screenshot; bool is_paused = runloop_ctl(RUNLOOP_CTL_IS_PAUSED, NULL); RARCH_LOG("%s.\n", msg); runloop_msg_queue_push(msg, 1, is_paused ? 1 : 180, true); if (is_paused) video_driver_cached_frame_render(); return ret; }