1 /**
2 * @file gfx.cpp
3 * @brief Platform-independent bitmap graphics transformation functionality
4 *
5 * (c) 2014 by Mega Limited, Auckland, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "mega.h"
23 #include "mega/gfx.h"
24
25 namespace mega {
26 const int GfxProc::dimensions[][2] = {
27 { 200, 0 }, // THUMBNAIL: square thumbnail, cropped from near center
28 { 1000, 1000 } // PREVIEW: scaled version inside 1000x1000 bounding square
29 };
30
31 const int GfxProc::dimensionsavatar[][2] = {
32 { 250, 0 } // AVATAR250X250: square thumbnail, cropped from near center
33 };
34
isgfx(string * localfilename)35 bool GfxProc::isgfx(string* localfilename)
36 {
37 char ext[8];
38 const char* supported;
39
40 if (!(supported = supportedformats()))
41 {
42 return true;
43 }
44
45 if (client->fsaccess->getextension(LocalPath::fromLocalname(*localfilename), ext, sizeof ext))
46 {
47 const char* ptr;
48
49 // FIXME: use hash
50 if ((ptr = strstr(supported, ext)) && ptr[strlen(ext)] == '.')
51 {
52 return true;
53 }
54 }
55
56 return false;
57 }
58
isvideo(string * localfilename)59 bool GfxProc::isvideo(string *localfilename)
60 {
61 char ext[8];
62 const char* supported;
63
64 if (!(supported = supportedvideoformats()))
65 {
66 return false;
67 }
68
69 if (client->fsaccess->getextension(LocalPath::fromLocalname(*localfilename), ext, sizeof ext))
70 {
71 const char* ptr;
72
73 // FIXME: use hash
74 if ((ptr = strstr(supported, ext)) && ptr[strlen(ext)] == '.')
75 {
76 return true;
77 }
78 }
79
80 return false;
81 }
82
supportedformats()83 const char* GfxProc::supportedformats()
84 {
85 return NULL;
86 }
87
supportedvideoformats()88 const char* GfxProc::supportedvideoformats()
89 {
90 return NULL;
91 }
92
threadEntryPoint(void * param)93 void *GfxProc::threadEntryPoint(void *param)
94 {
95 GfxProc* gfxProcessor = (GfxProc*)param;
96 gfxProcessor->loop();
97 return NULL;
98 }
99
loop()100 void GfxProc::loop()
101 {
102 GfxJob *job = NULL;
103 while (!finished)
104 {
105 waiter.init(NEVER);
106 waiter.wait();
107 while ((job = requests.pop()))
108 {
109 if (finished)
110 {
111 delete job;
112 break;
113 }
114
115 mutex.lock();
116 LOG_debug << "Processing media file: " << job->h;
117
118 // (this assumes that the width of the largest dimension is max)
119 if (readbitmap(NULL, &job->localfilename, dimensions[sizeof dimensions/sizeof dimensions[0]-1][0]))
120 {
121 for (unsigned i = 0; i < job->imagetypes.size(); i++)
122 {
123 // successively downscale the original image
124 string* jpeg = new string();
125 int w = dimensions[job->imagetypes[i]][0];
126 int h = dimensions[job->imagetypes[i]][1];
127
128 if (this->w < w && this->h < h)
129 {
130 LOG_debug << "Skipping upsizing of preview or thumbnail";
131 w = this->w;
132 h = this->h;
133 }
134
135 if (!resizebitmap(w, h, jpeg))
136 {
137 delete jpeg;
138 jpeg = NULL;
139 }
140 job->images.push_back(jpeg);
141 }
142 freebitmap();
143 }
144 else
145 {
146 for (unsigned i = 0; i < job->imagetypes.size(); i++)
147 {
148 job->images.push_back(NULL);
149 }
150 }
151
152 mutex.unlock();
153 responses.push(job);
154 client->waiter->notify();
155 }
156 }
157
158 while ((job = requests.pop()))
159 {
160 delete job;
161 }
162
163 while ((job = responses.pop()))
164 {
165 for (unsigned i = 0; i < job->imagetypes.size(); i++)
166 {
167 delete job->images[i];
168 }
169 delete job;
170 }
171 }
172
checkevents(Waiter *)173 int GfxProc::checkevents(Waiter *)
174 {
175 if (!client)
176 {
177 return 0;
178 }
179
180 GfxJob *job = NULL;
181 bool needexec = false;
182 while ((job = responses.pop()))
183 {
184 for (unsigned i = 0; i < job->images.size(); i++)
185 {
186 if (job->images[i])
187 {
188 LOG_debug << "Media file correctly processed. Attaching file attribute: " << job->h;
189
190 // store the file attribute data - it will be attached to the file
191 // immediately if the upload has already completed; otherwise, once
192 // the upload completes
193 mCheckEventsKey.setkey(job->key);
194 int creqtag = client->reqtag;
195 client->reqtag = 0;
196 client->putfa(job->h, job->imagetypes[i], &mCheckEventsKey, std::unique_ptr<string>(job->images[i]), job->flag);
197 client->reqtag = creqtag;
198 }
199 else
200 {
201 LOG_debug << "Unable to process media file: " << job->h;
202
203 Transfer *transfer = NULL;
204 handletransfer_map::iterator htit = client->faputcompletion.find(job->h);
205 if (htit != client->faputcompletion.end())
206 {
207 transfer = htit->second;
208 }
209 else
210 {
211 // check if the failed attribute belongs to an active upload
212 for (transfer_map::iterator it = client->transfers[PUT].begin(); it != client->transfers[PUT].end(); it++)
213 {
214 if (it->second->uploadhandle == job->h)
215 {
216 transfer = it->second;
217 break;
218 }
219 }
220 }
221
222 if (transfer)
223 {
224 // reduce the number of required attributes to let the upload continue
225 transfer->minfa--;
226 client->checkfacompletion(job->h);
227 }
228 else
229 {
230 LOG_debug << "Transfer related to media file not found: " << job->h;
231 }
232 }
233 needexec = true;
234 }
235 delete job;
236 }
237
238 return needexec ? Waiter::NEEDEXEC : 0;
239 }
240
transform(int & w,int & h,int & rw,int & rh,int & px,int & py)241 void GfxProc::transform(int& w, int& h, int& rw, int& rh, int& px, int& py)
242 {
243 if (rh)
244 {
245 // rectangular rw*rh bounding box
246 if (h*rw > w*rh)
247 {
248 w = w * rh / h;
249 h = rh;
250 }
251 else
252 {
253 h = h * rw / w;
254 w = rw;
255 }
256
257 px = 0;
258 py = 0;
259
260 rw = w;
261 rh = h;
262 }
263 else
264 {
265 // square rw*rw crop thumbnail
266 if (w < h)
267 {
268 h = h * rw / w;
269 w = rw;
270 }
271 else
272 {
273 w = w * rw / h;
274 h = rw;
275 }
276
277 px = (w - rw) / 2;
278 py = (h - rw) / 3;
279
280 rh = rw;
281 }
282 }
283
284 // load bitmap image, generate all designated sizes, attach to specified upload/node handle
285 // FIXME: move to a worker thread to keep the engine nonblocking
gendimensionsputfa(FileAccess *,string * localfilename,handle th,SymmCipher * key,int missing,bool checkAccess)286 int GfxProc::gendimensionsputfa(FileAccess* /*fa*/, string* localfilename, handle th, SymmCipher* key, int missing, bool checkAccess)
287 {
288 if (SimpleLogger::logCurrentLevel >= logDebug)
289 {
290 string utf8path;
291 client->fsaccess->local2path(localfilename, &utf8path);
292 LOG_debug << "Creating thumb/preview for " << utf8path;
293 }
294
295 GfxJob *job = new GfxJob();
296 job->h = th;
297 job->flag = checkAccess;
298 memcpy(job->key, key->key, SymmCipher::KEYLENGTH);
299 job->localfilename = *localfilename;
300 for (fatype i = sizeof dimensions/sizeof dimensions[0]; i--; )
301 {
302 if (missing & (1 << i))
303 {
304 job->imagetypes.push_back(i);
305 }
306 }
307
308 if (!job->imagetypes.size())
309 {
310 delete job;
311 return 0;
312 }
313
314 requests.push(job);
315 waiter.notify();
316 return int(job->imagetypes.size());
317 }
318
savefa(string * localfilepath,int width,int height,string * localdstpath)319 bool GfxProc::savefa(string *localfilepath, int width, int height, string *localdstpath)
320 {
321 if (!isgfx(localfilepath))
322 {
323 return false;
324 }
325
326 mutex.lock();
327 if (!readbitmap(NULL, localfilepath, width > height ? width : height))
328 {
329 mutex.unlock();
330 return false;
331 }
332
333 int w = width;
334 int h = height;
335 if (this->w < w && this->h < h)
336 {
337 LOG_debug << "Skipping upsizing of local preview";
338 w = this->w;
339 h = this->h;
340 }
341
342 string jpeg;
343 bool success = resizebitmap(w, h, &jpeg);
344 freebitmap();
345 mutex.unlock();
346
347 if (!success)
348 {
349 return false;
350 }
351
352 auto f = client->fsaccess->newfileaccess();
353 auto localpath = LocalPath::fromLocalname(*localdstpath);
354 client->fsaccess->unlinklocal(localpath);
355 if (!f->fopen(localpath, false, true))
356 {
357 return false;
358 }
359
360 if (!f->fwrite((const byte*)jpeg.data(), unsigned(jpeg.size()), 0))
361 {
362 return false;
363 }
364
365 return true;
366 }
367
GfxProc()368 GfxProc::GfxProc()
369 {
370 client = NULL;
371 finished = false;
372 }
373
startProcessingThread()374 void GfxProc::startProcessingThread()
375 {
376 thread.start(threadEntryPoint, this);
377 threadstarted = true;
378 }
379
~GfxProc()380 GfxProc::~GfxProc()
381 {
382 finished = true;
383 waiter.notify();
384 assert(threadstarted);
385 if (threadstarted)
386 {
387 thread.join();
388 }
389 }
390
GfxJobQueue()391 GfxJobQueue::GfxJobQueue()
392 {
393
394 }
395
push(GfxJob * job)396 void GfxJobQueue::push(GfxJob *job)
397 {
398 mutex.lock();
399 jobs.push_back(job);
400 mutex.unlock();
401 }
402
pop()403 GfxJob *GfxJobQueue::pop()
404 {
405 mutex.lock();
406 if (jobs.empty())
407 {
408 mutex.unlock();
409 return NULL;
410 }
411 GfxJob *job = jobs.front();
412 jobs.pop_front();
413 mutex.unlock();
414 return job;
415 }
416
GfxJob()417 GfxJob::GfxJob()
418 {
419
420 }
421
422 } // namespace
423