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