1 /*
2 * RGraphicsPlot.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include "RGraphicsPlot.hpp"
17
18 #include <iostream>
19
20 #include <boost/format.hpp>
21
22 #include <shared_core/Error.hpp>
23 #include <core/Log.hpp>
24
25 #include <core/system/System.hpp>
26 #include <core/StringUtils.hpp>
27
28 #include <r/RExec.hpp>
29 #include <r/session/RGraphics.hpp>
30
31 using namespace rstudio::core;
32
33 namespace rstudio {
34 namespace r {
35 namespace session {
36 namespace graphics {
37
Plot(const GraphicsDeviceFunctions & graphicsDevice,const FilePath & baseDirPath,SEXP manipulatorSEXP)38 Plot::Plot(const GraphicsDeviceFunctions& graphicsDevice,
39 const FilePath& baseDirPath,
40 SEXP manipulatorSEXP)
41 : graphicsDevice_(graphicsDevice),
42 baseDirPath_(baseDirPath),
43 needsUpdate_(false),
44 manipulator_(manipulatorSEXP)
45 {
46 }
47
Plot(const GraphicsDeviceFunctions & graphicsDevice,const FilePath & baseDirPath,const std::string & storageUuid,const DisplaySize & renderedSize)48 Plot::Plot(const GraphicsDeviceFunctions& graphicsDevice,
49 const FilePath& baseDirPath,
50 const std::string& storageUuid,
51 const DisplaySize& renderedSize)
52 : graphicsDevice_(graphicsDevice),
53 baseDirPath_(baseDirPath),
54 storageUuid_(storageUuid),
55 renderedSize_(renderedSize),
56 needsUpdate_(false),
57 manipulator_()
58 {
59 // invalidate if the image file doesn't exist (allows the server
60 // to migrate between different image backends e.g. png, jpeg, etc)
61 if (!imageFilePath(storageUuid_).exists())
62 invalidate();
63 }
64
storageUuid() const65 std::string Plot::storageUuid() const
66 {
67 return storageUuid_;
68 }
69
hasValidStorage() const70 bool Plot::hasValidStorage() const
71 {
72 return hasStorage() && snapshotFilePath().exists();
73 }
74
invalidate()75 void Plot::invalidate()
76 {
77 needsUpdate_ = true;
78 }
79
hasManipulator() const80 bool Plot::hasManipulator() const
81 {
82 // check is a bit complicated because defer loading the manipulator
83 // until it is asked for
84
85 return !manipulator_.empty() || hasManipulatorFile();
86 }
87
manipulatorSEXP() const88 SEXP Plot::manipulatorSEXP() const
89 {
90 if (hasManipulator())
91 {
92 loadManipulatorIfNecessary();
93 return manipulator_.sexp();
94 }
95 else
96 {
97 return R_NilValue;
98 }
99
100 return manipulator_.sexp();
101 }
102
manipulatorAsJson(json::Value * pValue) const103 void Plot::manipulatorAsJson(json::Value* pValue) const
104 {
105 if (hasManipulator())
106 {
107 loadManipulatorIfNecessary();
108 manipulator_.asJson(pValue);
109 }
110 else
111 {
112 *pValue = json::Value();
113 }
114 }
115
saveManipulator() const116 void Plot::saveManipulator() const
117 {
118 if (hasManipulator() && !storageUuid_.empty())
119 saveManipulator(storageUuid_);
120 }
121
renderFromDisplay()122 Error Plot::renderFromDisplay()
123 {
124 // we can use our cached representation if we don't need an update and our
125 // rendered size is the same as the current graphics device size
126 if ( !needsUpdate_ &&
127 (renderedSize() == graphicsDevice_.displaySize()) )
128 {
129 return Success();
130 }
131
132 // generate a new storage uuid
133 std::string storageUuid = core::system::generateUuid();
134
135 // generate snapshot and image files
136 FilePath snapshotPath = snapshotFilePath(storageUuid);
137 FilePath imagePath = imageFilePath(storageUuid);
138 Error error = graphicsDevice_.saveSnapshot(snapshotPath, imagePath);
139 if (error)
140 return Error(errc::PlotRenderingError, error, ERROR_LOCATION);
141
142 // save rendered size
143 renderedSize_ = graphicsDevice_.displaySize();
144
145 // save manipulator (if any)
146 saveManipulator(storageUuid);
147
148 // delete existing files (if any)
149 Error removeError = removeFiles();
150
151 // update state
152 storageUuid_ = storageUuid;
153 needsUpdate_ = false;
154
155 // return error status
156 return removeError;
157 }
158
renderFromDisplaySnapshot(SEXP snapshot)159 Error Plot::renderFromDisplaySnapshot(SEXP snapshot)
160 {
161 // if our baseDirPath_ no longer exists it means that someone
162 // has closed the graphics device underneath it, in this case
163 // just silently return success
164 if (!baseDirPath_.exists())
165 return Success();
166
167 // generate a new storage uuid
168 std::string storageUuid = core::system::generateUuid();
169
170 // generate snapshot file
171 FilePath snapshotFile = snapshotFilePath(storageUuid);
172 Error error = r::exec::RFunction(".rs.saveGraphicsSnapshot",
173 snapshot,
174 string_utils::utf8ToSystem(snapshotFile.getAbsolutePath())).call();
175 if (error)
176 return error;
177
178 //
179 // we can't generate an image file at this point in the processing
180 // because the GraphicsDevice has already moved on to the next page. this is
181 // OK though because we simply set needsUpdate_ = true below and the next
182 // time renderFromDisplay is called it will be rendered
183 //
184
185 // save rendered size
186 renderedSize_ = graphicsDevice_.displaySize();
187
188 // save manipulator (if any)
189 saveManipulator(storageUuid);
190
191 // delete existing files (if any)
192 Error removeError = removeFiles();
193
194 // update state
195 storageUuid_ = storageUuid;
196 needsUpdate_ = true;
197
198 // return error status
199 return removeError;
200 }
201
202
imageFilename() const203 std::string Plot::imageFilename() const
204 {
205 return imageFilePath(storageUuid()).getFilename();
206 }
207
renderToDisplay()208 Error Plot::renderToDisplay()
209 {
210 Error error = graphicsDevice_.restoreSnapshot(snapshotFilePath());
211 if (error)
212 {
213 Error graphicsError(errc::PlotRenderingError, error, ERROR_LOCATION);
214 DisplaySize deviceSize = graphicsDevice_.displaySize();
215 graphicsError.addProperty("device-width", deviceSize.width);
216 graphicsError.addProperty("device-height", deviceSize.height);
217 return graphicsError;
218 }
219 else
220 return Success();
221 }
222
removeFiles()223 Error Plot::removeFiles()
224 {
225 // bail if we don't have any storage
226 if (storageUuid_.empty())
227 return Success();
228
229 Error snapshotError = snapshotFilePath(storageUuid_).removeIfExists();
230 Error imageError = imageFilePath(storageUuid_).removeIfExists();
231 Error manipulatorError = manipulatorFilePath(storageUuid_).removeIfExists();
232
233 if (snapshotError)
234 return Error(errc::PlotFileError, snapshotError, ERROR_LOCATION);
235 else if (imageError)
236 return Error(errc::PlotFileError, imageError, ERROR_LOCATION);
237 else if (manipulatorError)
238 return Error(errc::PlotFileError, manipulatorError, ERROR_LOCATION);
239 else
240 return Success();
241 }
242
purgeInMemoryResources()243 void Plot::purgeInMemoryResources()
244 {
245 manipulator_.clear();
246 }
247
hasStorage() const248 bool Plot::hasStorage() const
249 {
250 return !storageUuid_.empty();
251 }
252
snapshotFilePath() const253 FilePath Plot::snapshotFilePath() const
254 {
255 return snapshotFilePath(storageUuid());
256 }
257
258
snapshotFilePath(const std::string & storageUuid) const259 FilePath Plot::snapshotFilePath(const std::string& storageUuid) const
260 {
261 return baseDirPath_.completePath(storageUuid + ".snapshot");
262 }
263
imageFilePath(const std::string & storageUuid) const264 FilePath Plot::imageFilePath(const std::string& storageUuid) const
265 {
266 std::string extension = graphicsDevice_.imageFileExtension();
267 return baseDirPath_.completePath(storageUuid + "." + extension);
268 }
269
hasManipulatorFile() const270 bool Plot::hasManipulatorFile() const
271 {
272 return hasStorage() && manipulatorFilePath(storageUuid()).exists();
273 }
274
manipulatorFilePath(const std::string & storageUuid) const275 FilePath Plot::manipulatorFilePath(const std::string& storageUuid) const
276 {
277 return baseDirPath_.completePath(storageUuid + ".manip");
278 }
279
loadManipulatorIfNecessary() const280 void Plot::loadManipulatorIfNecessary() const
281 {
282 if (manipulator_.empty() && hasManipulatorFile())
283 {
284 FilePath manipPath = manipulatorFilePath(storageUuid());
285 Error error = manipulator_.load(manipPath);
286 if (error)
287 LOG_ERROR(error);
288 }
289 }
290
saveManipulator(const std::string & storageUuid) const291 void Plot::saveManipulator(const std::string& storageUuid) const
292 {
293 loadManipulatorIfNecessary();
294 if (!manipulator_.empty())
295 {
296 Error error = manipulator_.save(manipulatorFilePath(storageUuid));
297 if (error)
298 LOG_ERROR(error);
299 }
300 }
301
302
303 } // namespace graphics
304 } // namespace session
305 } // namespace r
306 } // namespace rstudio
307
308
309
310