824c003d605842ee9dc96077dd2365dc27abbd15
[vuplus_xbmc] / xbmc / utils / Screenshot.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
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 2, or (at your option)
8  *  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 XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "Screenshot.h"
22
23 #include "system.h"
24 #include <vector>
25
26 #include "Util.h"
27
28 #include "Application.h"
29 #include "windowing/WindowingFactory.h"
30 #include "pictures/Picture.h"
31
32 #ifdef TARGET_RASPBERRY_PI
33 #include "xbmc/linux/RBP.h"
34 #endif
35
36 #ifdef HAS_VIDEO_PLAYBACK
37 #include "cores/VideoRenderers/RenderManager.h"
38 #endif
39
40 #include "filesystem/File.h"
41 #include "guilib/GraphicContext.h"
42
43 #include "utils/JobManager.h"
44 #include "utils/URIUtils.h"
45 #include "utils/log.h"
46 #include "settings/SettingPath.h"
47 #include "settings/Settings.h"
48 #include "settings/windows/GUIControlSettings.h"
49
50 using namespace std;
51 using namespace XFILE;
52
53 CScreenshotSurface::CScreenshotSurface()
54 {
55   m_width = 0;
56   m_height = 0;
57   m_stride = 0;
58   m_buffer = NULL;
59 }
60
61 bool CScreenshotSurface::capture()
62 {
63 #if defined(TARGET_RASPBERRY_PI)
64   g_RBP.GetDisplaySize(m_width, m_height);
65   m_buffer = g_RBP.CaptureDisplay(m_width, m_height, &m_stride, true, false);
66   if (!m_buffer)
67     return false;
68 #elif defined(HAS_DX)
69   LPDIRECT3DSURFACE9 lpSurface = NULL, lpBackbuffer = NULL;
70   g_graphicsContext.Lock();
71   if (g_application.m_pPlayer->IsPlayingVideo())
72   {
73 #ifdef HAS_VIDEO_PLAYBACK
74     g_renderManager.SetupScreenshot();
75 #endif
76   }
77   g_application.RenderNoPresent();
78
79   if (FAILED(g_Windowing.Get3DDevice()->CreateOffscreenPlainSurface(g_Windowing.GetWidth(), g_Windowing.GetHeight(), D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &lpSurface, NULL)))
80     return false;
81
82   if (FAILED(g_Windowing.Get3DDevice()->GetRenderTarget(0, &lpBackbuffer)))
83     return false;
84
85   // now take screenshot
86   if (SUCCEEDED(g_Windowing.Get3DDevice()->GetRenderTargetData(lpBackbuffer, lpSurface)))
87   {
88     D3DLOCKED_RECT lr;
89     D3DSURFACE_DESC desc;
90     lpSurface->GetDesc(&desc);
91     if (SUCCEEDED(lpSurface->LockRect(&lr, NULL, D3DLOCK_READONLY)))
92     {
93       m_width = desc.Width;
94       m_height = desc.Height;
95       m_stride = lr.Pitch;
96       m_buffer = new unsigned char[m_height * m_stride];
97       memcpy(m_buffer, lr.pBits, m_height * m_stride);
98       lpSurface->UnlockRect();
99     }
100     else
101     {
102       CLog::Log(LOGERROR, "%s LockRect failed", __FUNCTION__);
103     }
104   }
105   else
106   {
107     CLog::Log(LOGERROR, "%s GetBackBuffer failed", __FUNCTION__);
108   }
109   lpSurface->Release();
110   lpBackbuffer->Release();
111
112   g_graphicsContext.Unlock();
113
114 #elif defined(HAS_GL) || defined(HAS_GLES)
115
116   g_graphicsContext.BeginPaint();
117   if (g_application.m_pPlayer->IsPlayingVideo())
118   {
119 #ifdef HAS_VIDEO_PLAYBACK
120     g_renderManager.SetupScreenshot();
121 #endif
122   }
123   g_application.RenderNoPresent();
124 #ifndef HAS_GLES
125   glReadBuffer(GL_BACK);
126 #endif
127   //get current viewport
128   GLint viewport[4];
129   glGetIntegerv(GL_VIEWPORT, viewport);
130
131   m_width  = viewport[2] - viewport[0];
132   m_height = viewport[3] - viewport[1];
133   m_stride = m_width * 4;
134   unsigned char* surface = new unsigned char[m_stride * m_height];
135
136   //read pixels from the backbuffer
137 #if HAS_GLES == 2
138   glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)surface);
139 #else
140   glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, (GLvoid*)surface);
141 #endif
142   g_graphicsContext.EndPaint();
143
144   //make a new buffer and copy the read image to it with the Y axis inverted
145   m_buffer = new unsigned char[m_stride * m_height];
146   for (int y = 0; y < m_height; y++)
147   {
148 #ifdef HAS_GLES
149     // we need to save in BGRA order so XOR Swap RGBA -> BGRA
150     unsigned char* swap_pixels = surface + (m_height - y - 1) * m_stride;
151     for (int x = 0; x < m_width; x++, swap_pixels+=4)
152     {
153       std::swap(swap_pixels[0], swap_pixels[2]);
154     }   
155 #endif
156     memcpy(m_buffer + y * m_stride, surface + (m_height - y - 1) *m_stride, m_stride);
157   }
158
159   delete [] surface;
160
161 #else
162   //nothing to take a screenshot from
163   return false;
164 #endif
165
166   return true;
167 }
168
169 void CScreenShot::TakeScreenshot(const CStdString &filename, bool sync)
170 {
171
172   CScreenshotSurface surface;
173   if (!surface.capture())
174   {
175     CLog::Log(LOGERROR, "Screenshot %s failed", filename.c_str());
176     return;
177   }
178
179   CLog::Log(LOGDEBUG, "Saving screenshot %s", filename.c_str());
180
181   //set alpha byte to 0xFF
182   for (int y = 0; y < surface.m_height; y++)
183   {
184     unsigned char* alphaptr = surface.m_buffer - 1 + y * surface.m_stride;
185     for (int x = 0; x < surface.m_width; x++)
186       *(alphaptr += 4) = 0xFF;
187   }
188
189   //if sync is true, the png file needs to be completely written when this function returns
190   if (sync)
191   {
192     if (!CPicture::CreateThumbnailFromSurface(surface.m_buffer, surface.m_width, surface.m_height, surface.m_stride, filename))
193       CLog::Log(LOGERROR, "Unable to write screenshot %s", filename.c_str());
194
195     delete [] surface.m_buffer;
196   }
197   else
198   {
199     //make sure the file exists to avoid concurrency issues
200     FILE* fp = fopen(filename.c_str(), "w");
201     if (fp)
202       fclose(fp);
203     else
204       CLog::Log(LOGERROR, "Unable to create file %s", filename.c_str());
205
206     //write .png file asynchronous with CThumbnailWriter, prevents stalling of the render thread
207     //buffer is deleted from CThumbnailWriter
208     CThumbnailWriter* thumbnailwriter = new CThumbnailWriter(surface.m_buffer, surface.m_width, surface.m_height, surface.m_stride, filename);
209     CJobManager::GetInstance().AddJob(thumbnailwriter, NULL);
210   }
211 }
212
213 void CScreenShot::TakeScreenshot()
214 {
215   static bool savingScreenshots = false;
216   static vector<CStdString> screenShots;
217   bool promptUser = false;
218   CStdString strDir;
219
220   // check to see if we have a screenshot folder yet
221   CSettingPath *screenshotSetting = (CSettingPath*)CSettings::Get().GetSetting("debug.screenshotpath");
222   if (screenshotSetting != NULL)
223   {
224     strDir = screenshotSetting->GetValue();
225     if (strDir.empty())
226     {
227       if (CGUIControlButtonSetting::GetPath(screenshotSetting))
228         strDir = screenshotSetting->GetValue();
229     }
230   }
231
232   if (strDir.IsEmpty())
233   {
234     strDir = "special://temp/";
235     if (!savingScreenshots)
236     {
237       promptUser = true;
238       savingScreenshots = true;
239       screenShots.clear();
240     }
241   }
242   URIUtils::RemoveSlashAtEnd(strDir);
243
244   if (!strDir.IsEmpty())
245   {
246     CStdString file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(strDir, "screenshot%03d.png"), 999);
247
248     if (!file.IsEmpty())
249     {
250       TakeScreenshot(file, false);
251       if (savingScreenshots)
252         screenShots.push_back(file);
253       if (promptUser)
254       { // grab the real directory
255         CStdString newDir;
256         if (screenshotSetting != NULL)
257         {
258           newDir = screenshotSetting->GetValue();
259           if (newDir.empty())
260           {
261             if (CGUIControlButtonSetting::GetPath(screenshotSetting))
262               newDir = screenshotSetting->GetValue();
263           }
264         }
265
266         if (!newDir.IsEmpty())
267         {
268           for (unsigned int i = 0; i < screenShots.size(); i++)
269           {
270             CStdString file = CUtil::GetNextFilename(URIUtils::AddFileToFolder(newDir, "screenshot%03d.png"), 999);
271             CFile::Cache(screenShots[i], file);
272           }
273           screenShots.clear();
274         }
275         savingScreenshots = false;
276       }
277     }
278     else
279     {
280       CLog::Log(LOGWARNING, "Too many screen shots or invalid folder");
281     }
282   }
283 }