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