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.
12 #include "ServiceBroker.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"
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
);
40 !image
->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer
), width
, height
,
41 XB_FMT_A8R8G8B8
, stride
, thumbFile
, thumb
, thumbsize
))
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();
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
));
75 const bool ret
= file
.OpenForWrite(thumbFile
, true) &&
76 file
.Write(thumb
, thumbsize
) == static_cast<ssize_t
>(thumbsize
);
78 pImage
->ReleaseThumbnailBuffer();
84 CThumbnailWriter::CThumbnailWriter(unsigned char* buffer
, int width
, int height
, int stride
, const std::string
& thumbFile
):
85 m_thumbFile(thumbFile
)
93 CThumbnailWriter::~CThumbnailWriter()
98 bool CThumbnailWriter::DoWork()
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
));
115 bool CPicture::ResizeTexture(const std::string
& image
,
117 uint32_t& dest_width
,
118 uint32_t& dest_height
,
121 CPictureScalingAlgorithm::Algorithm
122 scalingAlgorithm
/* = CPictureScalingAlgorithm::NoAlgorithm */)
124 if (image
.empty() || texture
== NULL
)
127 return ResizeTexture(image
, texture
->GetPixels(), texture
->GetWidth(), texture
->GetHeight(), texture
->GetPitch(),
128 dest_width
, dest_height
, result
, result_size
,
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
)
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)
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
))
181 bool success
= GetThumbnailFromSurface((unsigned char*)buffer
, dest_width
, dest_height
, stride
,
182 image
, result
, result_size
);
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
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
)
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
))
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
);
264 { // no orientation needed
266 dest_height
= height
;
267 return CreateThumbnailFromSurface(pixels
, width
, height
, pitch
, dest
);
272 std::unique_ptr
<CTexture
> CPicture::CreateTiledThumb(const std::vector
<std::string
>& files
)
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
;
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
))
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);
328 std::unique_ptr
<CTexture
> result
= CTexture::CreateTexture();
330 result
->LoadFromMemory(imageRes
, imageRes
, imageRes
* 4, XB_FMT_A8R8G8B8
, true,
331 reinterpret_cast<unsigned char*>(buffer
.get()));
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
);
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
,
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 };
369 sws_scale(context
, src
, srcStride
, 0, in_height
, dst
, dstStride
);
370 sws_freeContext(context
);
376 bool CPicture::OrientateImage(std::unique_ptr
<uint32_t[]>& pixels
,
378 unsigned int& height
,
380 unsigned int& stridePixels
)
382 // ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c
387 out
= FlipHorizontal(pixels
, width
, height
, stridePixels
);
390 out
= Rotate180CCW(pixels
, width
, height
, stridePixels
);
393 out
= FlipVertical(pixels
, width
, height
, stridePixels
);
396 out
= Transpose(pixels
, width
, height
, stridePixels
);
399 out
= Rotate270CCW(pixels
, width
, height
, stridePixels
);
402 out
= TransposeOffAxis(pixels
, width
, height
, stridePixels
);
405 out
= Rotate90CCW(pixels
, width
, height
, stridePixels
);
408 CLog::Log(LOGERROR
, "Unknown orientation {}", orientation
);
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
]);
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
++);
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
--);
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
]);
467 bool CPicture::Rotate90CCW(std::unique_ptr
<uint32_t[]>& pixels
,
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
++)
484 pixels
= std::move(dest
);
485 std::swap(width
, height
);
486 stridePixels
= width
;
490 bool CPicture::Rotate270CCW(std::unique_ptr
<uint32_t[]>& pixels
,
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
++)
509 pixels
= std::move(dest
);
510 std::swap(width
, height
);
511 stridePixels
= width
;
515 bool CPicture::Transpose(std::unique_ptr
<uint32_t[]>& pixels
,
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
++)
533 pixels
= std::move(dest
);
534 std::swap(width
, height
);
535 stridePixels
= width
;
539 bool CPicture::TransposeOffAxis(std::unique_ptr
<uint32_t[]>& pixels
,
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
++)
558 pixels
= std::move(dest
);
559 std::swap(width
, height
);
560 stridePixels
= width
;