1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include <algorithm>
10 
11 #include "Picture.h"
12 #include "URL.h"
13 #include "ServiceBroker.h"
14 #include "settings/AdvancedSettings.h"
15 #include "settings/Settings.h"
16 #include "settings/SettingsComponent.h"
17 #include "FileItem.h"
18 #include "filesystem/File.h"
19 #include "utils/log.h"
20 #include "utils/URIUtils.h"
21 #include "guilib/Texture.h"
22 #include "guilib/imagefactory.h"
23 
24 extern "C" {
25 #include <libswscale/swscale.h>
26 }
27 
28 using namespace XFILE;
29 
GetThumbnailFromSurface(const unsigned char * buffer,int width,int height,int stride,const std::string & thumbFile,uint8_t * & result,size_t & result_size)30 bool CPicture::GetThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile, uint8_t* &result, size_t& result_size)
31 {
32   unsigned char *thumb = NULL;
33   unsigned int thumbsize = 0;
34 
35   // get an image handler
36   IImage* image = ImageFactory::CreateLoader(thumbFile);
37   if (image == NULL || !image->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, XB_FMT_A8R8G8B8, stride, thumbFile.c_str(), thumb, thumbsize))
38   {
39     delete image;
40     return false;
41   }
42 
43   // copy the resulting buffer
44   result_size = thumbsize;
45   result = new uint8_t[result_size];
46   memcpy(result, thumb, result_size);
47 
48   // release the image buffer and the image handler
49   image->ReleaseThumbnailBuffer();
50   delete image;
51 
52   return true;
53 }
54 
CreateThumbnailFromSurface(const unsigned char * buffer,int width,int height,int stride,const std::string & thumbFile)55 bool CPicture::CreateThumbnailFromSurface(const unsigned char *buffer, int width, int height, int stride, const std::string &thumbFile)
56 {
57   CLog::Log(LOGDEBUG, "cached image '%s' size %dx%d", CURL::GetRedacted(thumbFile).c_str(), width, height);
58 
59   unsigned char *thumb = NULL;
60   unsigned int thumbsize=0;
61   IImage* pImage = ImageFactory::CreateLoader(thumbFile);
62   if(pImage == NULL || !pImage->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, XB_FMT_A8R8G8B8, stride, thumbFile.c_str(), thumb, thumbsize))
63   {
64     CLog::Log(LOGERROR, "Failed to CreateThumbnailFromSurface for %s", CURL::GetRedacted(thumbFile).c_str());
65     delete pImage;
66     return false;
67   }
68 
69   XFILE::CFile file;
70   const bool ret = file.OpenForWrite(thumbFile, true) &&
71                    file.Write(thumb, thumbsize) == static_cast<ssize_t>(thumbsize);
72 
73   pImage->ReleaseThumbnailBuffer();
74   delete pImage;
75 
76   return ret;
77 }
78 
CThumbnailWriter(unsigned char * buffer,int width,int height,int stride,const std::string & thumbFile)79 CThumbnailWriter::CThumbnailWriter(unsigned char* buffer, int width, int height, int stride, const std::string& thumbFile):
80   m_thumbFile(thumbFile)
81 {
82   m_buffer    = buffer;
83   m_width     = width;
84   m_height    = height;
85   m_stride    = stride;
86 }
87 
~CThumbnailWriter()88 CThumbnailWriter::~CThumbnailWriter()
89 {
90   delete m_buffer;
91 }
92 
DoWork()93 bool CThumbnailWriter::DoWork()
94 {
95   bool success = true;
96 
97   if (!CPicture::CreateThumbnailFromSurface(m_buffer, m_width, m_height, m_stride, m_thumbFile))
98   {
99     CLog::Log(LOGERROR, "CThumbnailWriter::DoWork unable to write %s", CURL::GetRedacted(m_thumbFile).c_str());
100     success = false;
101   }
102 
103   delete [] m_buffer;
104   m_buffer = NULL;
105 
106   return success;
107 }
108 
ResizeTexture(const std::string & image,CTexture * texture,uint32_t & dest_width,uint32_t & dest_height,uint8_t * & result,size_t & result_size,CPictureScalingAlgorithm::Algorithm scalingAlgorithm)109 bool CPicture::ResizeTexture(const std::string& image,
110                              CTexture* texture,
111                              uint32_t& dest_width,
112                              uint32_t& dest_height,
113                              uint8_t*& result,
114                              size_t& result_size,
115                              CPictureScalingAlgorithm::Algorithm
116                                  scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
117 {
118   if (image.empty() || texture == NULL)
119     return false;
120 
121   return ResizeTexture(image, texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
122                        dest_width, dest_height, result, result_size,
123                        scalingAlgorithm);
124 }
125 
ResizeTexture(const std::string & image,uint8_t * pixels,uint32_t width,uint32_t height,uint32_t pitch,uint32_t & dest_width,uint32_t & dest_height,uint8_t * & result,size_t & result_size,CPictureScalingAlgorithm::Algorithm scalingAlgorithm)126 bool CPicture::ResizeTexture(const std::string &image, uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch,
127   uint32_t &dest_width, uint32_t &dest_height, uint8_t* &result, size_t& result_size,
128   CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
129 {
130   if (image.empty() || pixels == NULL)
131     return false;
132 
133   dest_width = std::min(width, dest_width);
134   dest_height = std::min(height, dest_height);
135 
136   // if no max width or height is specified, don't resize
137   if (dest_width == 0 && dest_height == 0)
138   {
139     dest_width = width;
140     dest_height = height;
141   }
142   else if (dest_width == 0)
143   {
144     double factor = (double)dest_height / (double)height;
145     dest_width = (uint32_t)(width * factor);
146   }
147   else if (dest_height == 0)
148   {
149     double factor = (double)dest_width / (double)width;
150     dest_height = (uint32_t)(height * factor);
151   }
152 
153   // nothing special to do if the dimensions already match
154   if (dest_width >= width || dest_height >= height)
155     return GetThumbnailFromSurface(pixels, dest_width, dest_height, pitch, image, result, result_size);
156 
157   // create a buffer large enough for the resulting image
158   GetScale(width, height, dest_width, dest_height);
159 
160   uint8_t *buffer = new uint8_t[dest_width * dest_height * sizeof(uint32_t)];
161   if (buffer == NULL)
162   {
163     result = NULL;
164     result_size = 0;
165     return false;
166   }
167 
168   if (!ScaleImage(pixels, width, height, pitch, buffer, dest_width, dest_height, dest_width * sizeof(uint32_t), scalingAlgorithm))
169   {
170     delete[] buffer;
171     result = NULL;
172     result_size = 0;
173     return false;
174   }
175 
176   bool success = GetThumbnailFromSurface(buffer, dest_width, dest_height, dest_width * sizeof(uint32_t), image, result, result_size);
177   delete[] buffer;
178 
179   if (!success)
180   {
181     result = NULL;
182     result_size = 0;
183   }
184 
185   return success;
186 }
187 
CacheTexture(CTexture * texture,uint32_t & dest_width,uint32_t & dest_height,const std::string & dest,CPictureScalingAlgorithm::Algorithm scalingAlgorithm)188 bool CPicture::CacheTexture(CTexture* texture,
189                             uint32_t& dest_width,
190                             uint32_t& dest_height,
191                             const std::string& dest,
192                             CPictureScalingAlgorithm::Algorithm
193                                 scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
194 {
195   return CacheTexture(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
196                       texture->GetOrientation(), dest_width, dest_height, dest, scalingAlgorithm);
197 }
198 
CacheTexture(uint8_t * pixels,uint32_t width,uint32_t height,uint32_t pitch,int orientation,uint32_t & dest_width,uint32_t & dest_height,const std::string & dest,CPictureScalingAlgorithm::Algorithm scalingAlgorithm)199 bool CPicture::CacheTexture(uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch, int orientation,
200   uint32_t &dest_width, uint32_t &dest_height, const std::string &dest,
201   CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
202 {
203   const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
204 
205   // if no max width or height is specified, don't resize
206   if (dest_width == 0)
207     dest_width = width;
208   if (dest_height == 0)
209     dest_height = height;
210   if (scalingAlgorithm == CPictureScalingAlgorithm::NoAlgorithm)
211     scalingAlgorithm = advancedSettings->m_imageScalingAlgorithm;
212 
213   uint32_t max_height = advancedSettings->m_imageRes;
214   if (advancedSettings->m_fanartRes > advancedSettings->m_imageRes)
215   { // 16x9 images larger than the fanart res use that rather than the image res
216     if (fabsf(static_cast<float>(width) / static_cast<float>(height) / (16.0f / 9.0f) - 1.0f)
217         <= 0.01f)
218     {
219       max_height = advancedSettings->m_fanartRes; // use height defined in fanartRes
220     }
221   }
222 
223   uint32_t max_width = max_height * 16/9;
224 
225   dest_height = std::min(dest_height, max_height);
226   dest_width  = std::min(dest_width, max_width);
227 
228   if (width > dest_width || height > dest_height || orientation)
229   {
230     bool success = false;
231 
232     dest_width = std::min(width, dest_width);
233     dest_height = std::min(height, dest_height);
234 
235     // create a buffer large enough for the resulting image
236     GetScale(width, height, dest_width, dest_height);
237     uint32_t *buffer = new uint32_t[dest_width * dest_height];
238     if (buffer)
239     {
240       if (ScaleImage(pixels, width, height, pitch,
241                      (uint8_t *)buffer, dest_width, dest_height, dest_width * 4,
242                      scalingAlgorithm))
243       {
244         if (!orientation || OrientateImage(buffer, dest_width, dest_height, orientation))
245         {
246           success = CreateThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height, dest_width * 4, dest);
247         }
248       }
249       delete[] buffer;
250     }
251     return success;
252   }
253   else
254   { // no orientation needed
255     dest_width = width;
256     dest_height = height;
257     return CreateThumbnailFromSurface(pixels, width, height, pitch, dest);
258   }
259   return false;
260 }
261 
CreateTiledThumb(const std::vector<std::string> & files,const std::string & thumb)262 bool CPicture::CreateTiledThumb(const std::vector<std::string> &files, const std::string &thumb)
263 {
264   if (!files.size())
265     return false;
266 
267   unsigned int num_across = (unsigned int)ceil(sqrt((float)files.size()));
268   unsigned int num_down = (files.size() + num_across - 1) / num_across;
269 
270   unsigned int imageRes = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes;
271 
272   unsigned int tile_width = imageRes / num_across;
273   unsigned int tile_height = imageRes / num_down;
274   unsigned int tile_gap = 1;
275   bool success = false;
276 
277   // create a buffer for the resulting thumb
278   uint32_t *buffer = static_cast<uint32_t *>(calloc(imageRes * imageRes, 4));
279   if (!buffer)
280     return false;
281   for (unsigned int i = 0; i < files.size(); ++i)
282   {
283     int x = i % num_across;
284     int y = i / num_across;
285     // load in the image
286     unsigned int width = tile_width - 2*tile_gap, height = tile_height - 2*tile_gap;
287     CTexture* texture = CTexture::LoadFromFile(files[i], width, height, true);
288     if (texture && texture->GetWidth() && texture->GetHeight())
289     {
290       GetScale(texture->GetWidth(), texture->GetHeight(), width, height);
291 
292       // scale appropriately
293       uint32_t *scaled = new uint32_t[width * height];
294       if (ScaleImage(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
295                      (uint8_t *)scaled, width, height, width * 4))
296       {
297         if (!texture->GetOrientation() || OrientateImage(scaled, width, height, texture->GetOrientation()))
298         {
299           success = true; // Flag that we at least had one successful image processed
300           // drop into the texture
301           unsigned int posX = x*tile_width + (tile_width - width)/2;
302           unsigned int posY = y*tile_height + (tile_height - height)/2;
303           uint32_t *dest = buffer + posX + posY * imageRes;
304           uint32_t *src = scaled;
305           for (unsigned int y = 0; y < height; ++y)
306           {
307             memcpy(dest, src, width*4);
308             dest += imageRes;
309             src += width;
310           }
311         }
312       }
313       delete[] scaled;
314     }
315     delete texture;
316   }
317   // now save to a file
318   if (success)
319     success = CreateThumbnailFromSurface((uint8_t *)buffer, imageRes, imageRes, imageRes * 4, thumb);
320 
321   free(buffer);
322   return success;
323 }
324 
GetScale(unsigned int width,unsigned int height,unsigned int & out_width,unsigned int & out_height)325 void CPicture::GetScale(unsigned int width, unsigned int height, unsigned int &out_width, unsigned int &out_height)
326 {
327   float aspect = (float)width / height;
328   if ((unsigned int)(out_width / aspect + 0.5f) > out_height)
329     out_width = (unsigned int)(out_height * aspect + 0.5f);
330   else
331     out_height = (unsigned int)(out_width / aspect + 0.5f);
332 }
333 
ScaleImage(uint8_t * in_pixels,unsigned int in_width,unsigned int in_height,unsigned int in_pitch,uint8_t * out_pixels,unsigned int out_width,unsigned int out_height,unsigned int out_pitch,CPictureScalingAlgorithm::Algorithm scalingAlgorithm)334 bool CPicture::ScaleImage(uint8_t *in_pixels, unsigned int in_width, unsigned int in_height, unsigned int in_pitch,
335                           uint8_t *out_pixels, unsigned int out_width, unsigned int out_height, unsigned int out_pitch,
336                           CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
337 {
338   struct SwsContext *context = sws_getContext(in_width, in_height, AV_PIX_FMT_BGRA,
339                                                          out_width, out_height, AV_PIX_FMT_BGRA,
340                                                          CPictureScalingAlgorithm::ToSwscale(scalingAlgorithm), NULL, NULL, NULL);
341 
342   uint8_t *src[] = { in_pixels, 0, 0, 0 };
343   int     srcStride[] = { (int)in_pitch, 0, 0, 0 };
344   uint8_t *dst[] = { out_pixels , 0, 0, 0 };
345   int     dstStride[] = { (int)out_pitch, 0, 0, 0 };
346 
347   if (context)
348   {
349     sws_scale(context, src, srcStride, 0, in_height, dst, dstStride);
350     sws_freeContext(context);
351     return true;
352   }
353   return false;
354 }
355 
OrientateImage(uint32_t * & pixels,unsigned int & width,unsigned int & height,int orientation)356 bool CPicture::OrientateImage(uint32_t *&pixels, unsigned int &width, unsigned int &height, int orientation)
357 {
358   // ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c
359   bool out = false;
360   switch (orientation)
361   {
362     case 1:
363       out = FlipHorizontal(pixels, width, height);
364       break;
365     case 2:
366       out = Rotate180CCW(pixels, width, height);
367       break;
368     case 3:
369       out = FlipVertical(pixels, width, height);
370       break;
371     case 4:
372       out = Transpose(pixels, width, height);
373       break;
374     case 5:
375       out = Rotate270CCW(pixels, width, height);
376       break;
377     case 6:
378       out = TransposeOffAxis(pixels, width, height);
379       break;
380     case 7:
381       out = Rotate90CCW(pixels, width, height);
382       break;
383     default:
384       CLog::Log(LOGERROR, "Unknown orientation %i", orientation);
385       break;
386   }
387   return out;
388 }
389 
FlipHorizontal(uint32_t * & pixels,unsigned int & width,unsigned int & height)390 bool CPicture::FlipHorizontal(uint32_t *&pixels, unsigned int &width, unsigned int &height)
391 {
392   // this can be done in-place easily enough
393   for (unsigned int y = 0; y < height; ++y)
394   {
395     uint32_t *line = pixels + y * width;
396     for (unsigned int x = 0; x < width / 2; ++x)
397       std::swap(line[x], line[width - 1 - x]);
398   }
399   return true;
400 }
401 
FlipVertical(uint32_t * & pixels,unsigned int & width,unsigned int & height)402 bool CPicture::FlipVertical(uint32_t *&pixels, unsigned int &width, unsigned int &height)
403 {
404   // this can be done in-place easily enough
405   for (unsigned int y = 0; y < height / 2; ++y)
406   {
407     uint32_t *line1 = pixels + y * width;
408     uint32_t *line2 = pixels + (height - 1 - y) * width;
409     for (unsigned int x = 0; x < width; ++x)
410       std::swap(*line1++, *line2++);
411   }
412   return true;
413 }
414 
Rotate180CCW(uint32_t * & pixels,unsigned int & width,unsigned int & height)415 bool CPicture::Rotate180CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
416 {
417   // this can be done in-place easily enough
418   for (unsigned int y = 0; y < height / 2; ++y)
419   {
420     uint32_t *line1 = pixels + y * width;
421     uint32_t *line2 = pixels + (height - 1 - y) * width + width - 1;
422     for (unsigned int x = 0; x < width; ++x)
423       std::swap(*line1++, *line2--);
424   }
425   if (height % 2)
426   { // height is odd, so flip the middle row as well
427     uint32_t *line = pixels + (height - 1)/2 * width;
428     for (unsigned int x = 0; x < width / 2; ++x)
429       std::swap(line[x], line[width - 1 - x]);
430   }
431   return true;
432 }
433 
Rotate90CCW(uint32_t * & pixels,unsigned int & width,unsigned int & height)434 bool CPicture::Rotate90CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
435 {
436   uint32_t *dest = new uint32_t[width * height * 4];
437   if (dest)
438   {
439     unsigned int d_height = width, d_width = height;
440     for (unsigned int y = 0; y < d_height; y++)
441     {
442       const uint32_t *src = pixels + (d_height - 1 - y); // y-th col from right, starting at top
443       uint32_t *dst = dest + d_width * y;                // y-th row from top, starting at left
444       for (unsigned int x = 0; x < d_width; x++)
445       {
446         *dst++ = *src;
447         src += width;
448       }
449     }
450     delete[] pixels;
451     pixels = dest;
452     std::swap(width, height);
453     return true;
454   }
455   return false;
456 }
457 
Rotate270CCW(uint32_t * & pixels,unsigned int & width,unsigned int & height)458 bool CPicture::Rotate270CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
459 {
460   uint32_t *dest = new uint32_t[width * height * 4];
461   if (!dest)
462     return false;
463 
464   unsigned int d_height = width, d_width = height;
465   for (unsigned int y = 0; y < d_height; y++)
466   {
467     const uint32_t *src = pixels + width * (d_width - 1) + y; // y-th col from left, starting at bottom
468     uint32_t *dst = dest + d_width * y;                       // y-th row from top, starting at left
469     for (unsigned int x = 0; x < d_width; x++)
470     {
471       *dst++ = *src;
472       src -= width;
473     }
474   }
475 
476   delete[] pixels;
477   pixels = dest;
478   std::swap(width, height);
479   return true;
480 }
481 
Transpose(uint32_t * & pixels,unsigned int & width,unsigned int & height)482 bool CPicture::Transpose(uint32_t *&pixels, unsigned int &width, unsigned int &height)
483 {
484   uint32_t *dest = new uint32_t[width * height * 4];
485   if (!dest)
486     return false;
487 
488   unsigned int d_height = width, d_width = height;
489   for (unsigned int y = 0; y < d_height; y++)
490   {
491     const uint32_t *src = pixels + y;   // y-th col from left, starting at top
492     uint32_t *dst = dest + d_width * y; // y-th row from top, starting at left
493     for (unsigned int x = 0; x < d_width; x++)
494     {
495       *dst++ = *src;
496       src += width;
497     }
498   }
499 
500   delete[] pixels;
501   pixels = dest;
502   std::swap(width, height);
503   return true;
504 }
505 
TransposeOffAxis(uint32_t * & pixels,unsigned int & width,unsigned int & height)506 bool CPicture::TransposeOffAxis(uint32_t *&pixels, unsigned int &width, unsigned int &height)
507 {
508   uint32_t *dest = new uint32_t[width * height * 4];
509   if (!dest)
510     return false;
511 
512   unsigned int d_height = width, d_width = height;
513   for (unsigned int y = 0; y < d_height; y++)
514   {
515     const uint32_t *src = pixels + width * (d_width - 1) + (d_height - 1 - y); // y-th col from right, starting at bottom
516     uint32_t *dst = dest + d_width * y;                                        // y-th row, starting at left
517     for (unsigned int x = 0; x < d_width; x++)
518     {
519       *dst++ = *src;
520       src -= width;
521     }
522   }
523 
524   delete[] pixels;
525   pixels = dest;
526   std::swap(width, height);
527   return true;
528 }
529