Merge pull request #26166 from ksooo/improve-plugin-ctx-menus
[xbmc.git] / xbmc / pictures / Picture.cpp
blob4a626fc4e42da8bcd6d530a8a0c6b4b96954496d
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "Picture.h"
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "filesystem/File.h"
15 #include "guilib/Texture.h"
16 #include "guilib/imagefactory.h"
17 #include "settings/AdvancedSettings.h"
18 #include "settings/Settings.h"
19 #include "settings/SettingsComponent.h"
20 #include "utils/MemUtils.h"
21 #include "utils/URIUtils.h"
22 #include "utils/log.h"
24 #include <algorithm>
26 extern "C" {
27 #include <libswscale/swscale.h>
30 using namespace XFILE;
32 bool CPicture::GetThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile, uint8_t* &result, size_t& result_size)
34 unsigned char *thumb = NULL;
35 unsigned int thumbsize = 0;
37 // get an image handler
38 IImage* image = ImageFactory::CreateLoader(thumbFile);
39 if (image == NULL ||
40 !image->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height,
41 XB_FMT_A8R8G8B8, stride, thumbFile, thumb, thumbsize))
43 delete image;
44 return false;
47 // copy the resulting buffer
48 result_size = thumbsize;
49 result = new uint8_t[result_size];
50 memcpy(result, thumb, result_size);
52 // release the image buffer and the image handler
53 image->ReleaseThumbnailBuffer();
54 delete image;
56 return true;
59 bool CPicture::CreateThumbnailFromSurface(const unsigned char *buffer, int width, int height, int stride, const std::string &thumbFile)
61 CLog::Log(LOGDEBUG, "cached image '{}' size {}x{}", CURL::GetRedacted(thumbFile), width, height);
63 unsigned char *thumb = NULL;
64 unsigned int thumbsize=0;
65 IImage* pImage = ImageFactory::CreateLoader(thumbFile);
66 if(pImage == NULL || !pImage->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, XB_FMT_A8R8G8B8, stride, thumbFile.c_str(), thumb, thumbsize))
68 CLog::Log(LOGERROR, "Failed to CreateThumbnailFromSurface for {}",
69 CURL::GetRedacted(thumbFile));
70 delete pImage;
71 return false;
74 XFILE::CFile file;
75 const bool ret = file.OpenForWrite(thumbFile, true) &&
76 file.Write(thumb, thumbsize) == static_cast<ssize_t>(thumbsize);
78 pImage->ReleaseThumbnailBuffer();
79 delete pImage;
81 return ret;
84 CThumbnailWriter::CThumbnailWriter(unsigned char* buffer, int width, int height, int stride, const std::string& thumbFile):
85 m_thumbFile(thumbFile)
87 m_buffer = buffer;
88 m_width = width;
89 m_height = height;
90 m_stride = stride;
93 CThumbnailWriter::~CThumbnailWriter()
95 delete m_buffer;
98 bool CThumbnailWriter::DoWork()
100 bool success = true;
102 if (!CPicture::CreateThumbnailFromSurface(m_buffer, m_width, m_height, m_stride, m_thumbFile))
104 CLog::Log(LOGERROR, "CThumbnailWriter::DoWork unable to write {}",
105 CURL::GetRedacted(m_thumbFile));
106 success = false;
109 delete [] m_buffer;
110 m_buffer = NULL;
112 return success;
115 bool CPicture::ResizeTexture(const std::string& image,
116 CTexture* texture,
117 uint32_t& dest_width,
118 uint32_t& dest_height,
119 uint8_t*& result,
120 size_t& result_size,
121 CPictureScalingAlgorithm::Algorithm
122 scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
124 if (image.empty() || texture == NULL)
125 return false;
127 return ResizeTexture(image, texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
128 dest_width, dest_height, result, result_size,
129 scalingAlgorithm);
132 bool CPicture::ResizeTexture(const std::string &image, uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch,
133 uint32_t &dest_width, uint32_t &dest_height, uint8_t* &result, size_t& result_size,
134 CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
136 if (image.empty() || pixels == NULL)
137 return false;
139 dest_width = std::min(width, dest_width);
140 dest_height = std::min(height, dest_height);
142 // if no max width or height is specified, don't resize
143 if (dest_width == 0 && dest_height == 0)
145 dest_width = width;
146 dest_height = height;
148 else if (dest_width == 0)
150 double factor = (double)dest_height / (double)height;
151 dest_width = (uint32_t)(width * factor);
153 else if (dest_height == 0)
155 double factor = (double)dest_width / (double)width;
156 dest_height = (uint32_t)(height * factor);
159 // nothing special to do if the dimensions already match
160 if (dest_width >= width || dest_height >= height)
161 return GetThumbnailFromSurface(pixels, dest_width, dest_height, pitch, image, result, result_size);
163 // create a buffer large enough for the resulting image
164 GetScale(width, height, dest_width, dest_height);
166 // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top
167 // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157
168 uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f);
169 uint32_t stride = dest_width_aligned * sizeof(uint32_t);
171 uint32_t* buffer = new uint32_t[dest_width_aligned * dest_height + 4];
172 if (!ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA, (uint8_t*)buffer, dest_width,
173 dest_height, stride, AV_PIX_FMT_BGRA, scalingAlgorithm))
175 delete[] buffer;
176 result = NULL;
177 result_size = 0;
178 return false;
181 bool success = GetThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height, stride,
182 image, result, result_size);
183 delete[] buffer;
185 if (!success)
187 result = NULL;
188 result_size = 0;
191 return success;
194 bool CPicture::CacheTexture(CTexture* texture,
195 uint32_t& dest_width,
196 uint32_t& dest_height,
197 const std::string& dest,
198 CPictureScalingAlgorithm::Algorithm
199 scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
201 return CacheTexture(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
202 texture->GetOrientation(), dest_width, dest_height, dest, scalingAlgorithm);
205 bool CPicture::CacheTexture(uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch, int orientation,
206 uint32_t &dest_width, uint32_t &dest_height, const std::string &dest,
207 CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
209 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
211 // if no max width or height is specified, don't resize
212 if (dest_width == 0)
213 dest_width = width;
214 if (dest_height == 0)
215 dest_height = height;
216 if (scalingAlgorithm == CPictureScalingAlgorithm::NoAlgorithm)
217 scalingAlgorithm = advancedSettings->m_imageScalingAlgorithm;
219 uint32_t max_height = advancedSettings->m_imageRes;
220 if (advancedSettings->m_fanartRes > advancedSettings->m_imageRes)
221 { // 16x9 images larger than the fanart res use that rather than the image res
222 if (fabsf(static_cast<float>(width) / static_cast<float>(height) / (16.0f / 9.0f) - 1.0f)
223 <= 0.01f)
225 max_height = advancedSettings->m_fanartRes; // use height defined in fanartRes
229 uint32_t max_width = max_height * 16/9;
231 dest_height = std::min(dest_height, max_height);
232 dest_width = std::min(dest_width, max_width);
234 if (width > dest_width || height > dest_height || orientation)
236 bool success = false;
238 dest_width = std::min(width, dest_width);
239 dest_height = std::min(height, dest_height);
241 // create a buffer large enough for the resulting image
242 GetScale(width, height, dest_width, dest_height);
244 // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top
245 // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157
246 uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f);
247 uint32_t stride = dest_width_aligned * sizeof(uint32_t);
249 auto buffer = std::make_unique<uint32_t[]>(dest_width_aligned * dest_height + 4);
250 if (ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA,
251 reinterpret_cast<uint8_t*>(buffer.get()), dest_width, dest_height, stride,
252 AV_PIX_FMT_BGRA, scalingAlgorithm))
254 if (!orientation ||
255 OrientateImage(buffer, dest_width, dest_height, orientation, dest_width_aligned))
257 success = CreateThumbnailFromSurface(reinterpret_cast<unsigned char*>(buffer.get()),
258 dest_width, dest_height, dest_width_aligned * 4, dest);
261 return success;
263 else
264 { // no orientation needed
265 dest_width = width;
266 dest_height = height;
267 return CreateThumbnailFromSurface(pixels, width, height, pitch, dest);
269 return false;
272 std::unique_ptr<CTexture> CPicture::CreateTiledThumb(const std::vector<std::string>& files)
274 if (!files.size())
275 return {};
277 unsigned int num_across =
278 static_cast<unsigned int>(std::ceil(std::sqrt(static_cast<float>(files.size()))));
279 unsigned int num_down = (files.size() + num_across - 1) / num_across;
281 unsigned int imageRes = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes;
283 unsigned int tile_width = imageRes / num_across;
284 unsigned int tile_height = imageRes / num_down;
285 unsigned int tile_gap = 1;
286 bool success = false; // Flag that we at least had one successful image processed
288 // create a buffer for the resulting thumb
289 std::unique_ptr<uint32_t[]> buffer = std::make_unique<uint32_t[]>(imageRes * imageRes);
290 for (unsigned int i = 0; i < files.size(); ++i)
292 int x = i % num_across;
293 int y = i / num_across;
294 // load in the image
295 unsigned int width = tile_width - 2 * tile_gap, height = tile_height - 2 * tile_gap;
296 std::unique_ptr<CTexture> texture = CTexture::LoadFromFile(files[i], width, height);
297 if (texture && texture->GetWidth() && texture->GetHeight())
299 GetScale(texture->GetWidth(), texture->GetHeight(), width, height);
301 // scale appropriately
302 std::unique_ptr<uint32_t[]> scaled = std::make_unique<uint32_t[]>(width * height);
303 if (ScaleImage(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(),
304 texture->GetPitch(), AV_PIX_FMT_BGRA, reinterpret_cast<uint8_t*>(scaled.get()),
305 width, height, width * 4, AV_PIX_FMT_BGRA))
307 unsigned int stridePixels{width};
308 if (!texture->GetOrientation() ||
309 OrientateImage(scaled, width, height, texture->GetOrientation(), stridePixels))
311 success = true;
312 // drop into the texture
313 unsigned int posX = x * tile_width + (tile_width - width) / 2;
314 unsigned int posY = y * tile_height + (tile_height - height) / 2;
315 uint32_t* dest = buffer.get() + posX + posY * imageRes;
316 const uint32_t* src = scaled.get();
317 for (unsigned int y = 0; y < height; ++y)
319 memcpy(dest, src, width * 4);
320 dest += imageRes;
321 src += stridePixels;
328 std::unique_ptr<CTexture> result = CTexture::CreateTexture();
329 if (success)
330 result->LoadFromMemory(imageRes, imageRes, imageRes * 4, XB_FMT_A8R8G8B8, true,
331 reinterpret_cast<unsigned char*>(buffer.get()));
333 return result;
336 void CPicture::GetScale(unsigned int width, unsigned int height, unsigned int &out_width, unsigned int &out_height)
338 float aspect = (float)width / height;
339 if ((unsigned int)(out_width / aspect + 0.5f) > out_height)
340 out_width = (unsigned int)(out_height * aspect + 0.5f);
341 else
342 out_height = (unsigned int)(out_width / aspect + 0.5f);
345 bool CPicture::ScaleImage(uint8_t* in_pixels,
346 unsigned int in_width,
347 unsigned int in_height,
348 unsigned int in_pitch,
349 AVPixelFormat in_format,
350 uint8_t* out_pixels,
351 unsigned int out_width,
352 unsigned int out_height,
353 unsigned int out_pitch,
354 AVPixelFormat out_format,
355 CPictureScalingAlgorithm::Algorithm
356 scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
358 struct SwsContext* context =
359 sws_getContext(in_width, in_height, in_format, out_width, out_height, out_format,
360 CPictureScalingAlgorithm::ToSwscale(scalingAlgorithm), NULL, NULL, NULL);
362 uint8_t *src[] = { in_pixels, 0, 0, 0 };
363 int srcStride[] = { (int)in_pitch, 0, 0, 0 };
364 uint8_t *dst[] = { out_pixels , 0, 0, 0 };
365 int dstStride[] = { (int)out_pitch, 0, 0, 0 };
367 if (context)
369 sws_scale(context, src, srcStride, 0, in_height, dst, dstStride);
370 sws_freeContext(context);
371 return true;
373 return false;
376 bool CPicture::OrientateImage(std::unique_ptr<uint32_t[]>& pixels,
377 unsigned int& width,
378 unsigned int& height,
379 int orientation,
380 unsigned int& stridePixels)
382 // ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c
383 bool out = false;
384 switch (orientation)
386 case 1:
387 out = FlipHorizontal(pixels, width, height, stridePixels);
388 break;
389 case 2:
390 out = Rotate180CCW(pixels, width, height, stridePixels);
391 break;
392 case 3:
393 out = FlipVertical(pixels, width, height, stridePixels);
394 break;
395 case 4:
396 out = Transpose(pixels, width, height, stridePixels);
397 break;
398 case 5:
399 out = Rotate270CCW(pixels, width, height, stridePixels);
400 break;
401 case 6:
402 out = TransposeOffAxis(pixels, width, height, stridePixels);
403 break;
404 case 7:
405 out = Rotate90CCW(pixels, width, height, stridePixels);
406 break;
407 default:
408 CLog::Log(LOGERROR, "Unknown orientation {}", orientation);
409 break;
411 return out;
414 bool CPicture::FlipHorizontal(std::unique_ptr<uint32_t[]>& pixels,
415 const unsigned int& width,
416 const unsigned int& height,
417 const unsigned int& stridePixels)
419 // this can be done in-place easily enough
420 for (unsigned int y = 0; y < height; ++y)
422 uint32_t* line = pixels.get() + y * stridePixels;
423 for (unsigned int x = 0; x < width / 2; ++x)
424 std::swap(line[x], line[width - 1 - x]);
426 return true;
429 bool CPicture::FlipVertical(std::unique_ptr<uint32_t[]>& pixels,
430 const unsigned int& width,
431 const unsigned int& height,
432 const unsigned int& stridePixels)
434 // this can be done in-place easily enough
435 for (unsigned int y = 0; y < height / 2; ++y)
437 uint32_t* line1 = pixels.get() + y * stridePixels;
438 uint32_t* line2 = pixels.get() + (height - 1 - y) * stridePixels;
439 for (unsigned int x = 0; x < width; ++x)
440 std::swap(*line1++, *line2++);
442 return true;
445 bool CPicture::Rotate180CCW(std::unique_ptr<uint32_t[]>& pixels,
446 const unsigned int& width,
447 const unsigned int& height,
448 const unsigned int& stridePixels)
450 // this can be done in-place easily enough
451 for (unsigned int y = 0; y < height / 2; ++y)
453 uint32_t* line1 = pixels.get() + y * stridePixels;
454 uint32_t* line2 = pixels.get() + (height - 1 - y) * stridePixels + width - 1;
455 for (unsigned int x = 0; x < width; ++x)
456 std::swap(*line1++, *line2--);
458 if (height % 2)
459 { // height is odd, so flip the middle row as well
460 uint32_t* line = pixels.get() + (height - 1) / 2 * stridePixels;
461 for (unsigned int x = 0; x < width / 2; ++x)
462 std::swap(line[x], line[width - 1 - x]);
464 return true;
467 bool CPicture::Rotate90CCW(std::unique_ptr<uint32_t[]>& pixels,
468 unsigned int& width,
469 unsigned int& height,
470 unsigned int& stridePixels)
472 auto dest = std::make_unique<uint32_t[]>(width * height * 4);
473 unsigned int d_height = width, d_width = height;
474 for (unsigned int y = 0; y < d_height; y++)
476 const uint32_t* src = pixels.get() + (d_height - 1 - y); // y-th col from right, starting at top
477 uint32_t* dst = dest.get() + d_width * y; // y-th row from top, starting at left
478 for (unsigned int x = 0; x < d_width; x++)
480 *dst++ = *src;
481 src += stridePixels;
484 pixels = std::move(dest);
485 std::swap(width, height);
486 stridePixels = width;
487 return true;
490 bool CPicture::Rotate270CCW(std::unique_ptr<uint32_t[]>& pixels,
491 unsigned int& width,
492 unsigned int& height,
493 unsigned int& stridePixels)
495 auto dest = std::make_unique<uint32_t[]>(width * height * 4);
496 unsigned int d_height = width, d_width = height;
497 for (unsigned int y = 0; y < d_height; y++)
499 const uint32_t* src =
500 pixels.get() + stridePixels * (d_width - 1) + y; // y-th col from left, starting at bottom
501 uint32_t* dst = dest.get() + d_width * y; // y-th row from top, starting at left
502 for (unsigned int x = 0; x < d_width; x++)
504 *dst++ = *src;
505 src -= stridePixels;
509 pixels = std::move(dest);
510 std::swap(width, height);
511 stridePixels = width;
512 return true;
515 bool CPicture::Transpose(std::unique_ptr<uint32_t[]>& pixels,
516 unsigned int& width,
517 unsigned int& height,
518 unsigned int& stridePixels)
520 auto dest = std::make_unique<uint32_t[]>(width * height * 4);
521 unsigned int d_height = width, d_width = height;
522 for (unsigned int y = 0; y < d_height; y++)
524 const uint32_t* src = pixels.get() + y; // y-th col from left, starting at top
525 uint32_t* dst = dest.get() + d_width * y; // y-th row from top, starting at left
526 for (unsigned int x = 0; x < d_width; x++)
528 *dst++ = *src;
529 src += stridePixels;
533 pixels = std::move(dest);
534 std::swap(width, height);
535 stridePixels = width;
536 return true;
539 bool CPicture::TransposeOffAxis(std::unique_ptr<uint32_t[]>& pixels,
540 unsigned int& width,
541 unsigned int& height,
542 unsigned int& stridePixels)
544 auto dest = std::make_unique<uint32_t[]>(width * height * 4);
545 unsigned int d_height = width, d_width = height;
546 for (unsigned int y = 0; y < d_height; y++)
548 const uint32_t* src = pixels.get() + stridePixels * (d_width - 1) +
549 (d_height - 1 - y); // y-th col from right, starting at bottom
550 uint32_t* dst = dest.get() + d_width * y; // y-th row, starting at left
551 for (unsigned int x = 0; x < d_width; x++)
553 *dst++ = *src;
554 src -= stridePixels;
558 pixels = std::move(dest);
559 std::swap(width, height);
560 stridePixels = width;
561 return true;