diff options
Diffstat (limited to 'xbmc/pictures/Picture.cpp')
-rw-r--r-- | xbmc/pictures/Picture.cpp | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/xbmc/pictures/Picture.cpp b/xbmc/pictures/Picture.cpp new file mode 100644 index 0000000..9c7ec1e --- /dev/null +++ b/xbmc/pictures/Picture.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include <algorithm> + +#include "Picture.h" +#include "URL.h" +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "FileItem.h" +#include "filesystem/File.h" +#include "utils/log.h" +#include "utils/URIUtils.h" +#include "guilib/Texture.h" +#include "guilib/imagefactory.h" + +extern "C" { +#include <libswscale/swscale.h> +} + +using namespace XFILE; + +bool CPicture::GetThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile, uint8_t* &result, size_t& result_size) +{ + unsigned char *thumb = NULL; + unsigned int thumbsize = 0; + + // get an image handler + IImage* image = ImageFactory::CreateLoader(thumbFile); + if (image == NULL || + !image->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, + XB_FMT_A8R8G8B8, stride, thumbFile, thumb, thumbsize)) + { + delete image; + return false; + } + + // copy the resulting buffer + result_size = thumbsize; + result = new uint8_t[result_size]; + memcpy(result, thumb, result_size); + + // release the image buffer and the image handler + image->ReleaseThumbnailBuffer(); + delete image; + + return true; +} + +bool CPicture::CreateThumbnailFromSurface(const unsigned char *buffer, int width, int height, int stride, const std::string &thumbFile) +{ + CLog::Log(LOGDEBUG, "cached image '{}' size {}x{}", CURL::GetRedacted(thumbFile), width, height); + + unsigned char *thumb = NULL; + unsigned int thumbsize=0; + IImage* pImage = ImageFactory::CreateLoader(thumbFile); + if(pImage == NULL || !pImage->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, XB_FMT_A8R8G8B8, stride, thumbFile.c_str(), thumb, thumbsize)) + { + CLog::Log(LOGERROR, "Failed to CreateThumbnailFromSurface for {}", + CURL::GetRedacted(thumbFile)); + delete pImage; + return false; + } + + XFILE::CFile file; + const bool ret = file.OpenForWrite(thumbFile, true) && + file.Write(thumb, thumbsize) == static_cast<ssize_t>(thumbsize); + + pImage->ReleaseThumbnailBuffer(); + delete pImage; + + return ret; +} + +CThumbnailWriter::CThumbnailWriter(unsigned char* buffer, int width, int height, int stride, const std::string& thumbFile): + m_thumbFile(thumbFile) +{ + m_buffer = buffer; + m_width = width; + m_height = height; + m_stride = stride; +} + +CThumbnailWriter::~CThumbnailWriter() +{ + delete m_buffer; +} + +bool CThumbnailWriter::DoWork() +{ + bool success = true; + + if (!CPicture::CreateThumbnailFromSurface(m_buffer, m_width, m_height, m_stride, m_thumbFile)) + { + CLog::Log(LOGERROR, "CThumbnailWriter::DoWork unable to write {}", + CURL::GetRedacted(m_thumbFile)); + success = false; + } + + delete [] m_buffer; + m_buffer = NULL; + + return success; +} + +bool CPicture::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 /* = CPictureScalingAlgorithm::NoAlgorithm */) +{ + if (image.empty() || texture == NULL) + return false; + + return ResizeTexture(image, texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(), + dest_width, dest_height, result, result_size, + scalingAlgorithm); +} + +bool CPicture::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 /* = CPictureScalingAlgorithm::NoAlgorithm */) +{ + if (image.empty() || pixels == NULL) + return false; + + dest_width = std::min(width, dest_width); + dest_height = std::min(height, dest_height); + + // if no max width or height is specified, don't resize + if (dest_width == 0 && dest_height == 0) + { + dest_width = width; + dest_height = height; + } + else if (dest_width == 0) + { + double factor = (double)dest_height / (double)height; + dest_width = (uint32_t)(width * factor); + } + else if (dest_height == 0) + { + double factor = (double)dest_width / (double)width; + dest_height = (uint32_t)(height * factor); + } + + // nothing special to do if the dimensions already match + if (dest_width >= width || dest_height >= height) + return GetThumbnailFromSurface(pixels, dest_width, dest_height, pitch, image, result, result_size); + + // create a buffer large enough for the resulting image + GetScale(width, height, dest_width, dest_height); + + // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top + // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157 + uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f); + uint32_t stride = dest_width_aligned * sizeof(uint32_t); + + uint32_t* buffer = new uint32_t[dest_width_aligned * dest_height + 4]; + if (buffer == NULL) + { + result = NULL; + result_size = 0; + return false; + } + + if (!ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA, (uint8_t*)buffer, dest_width, + dest_height, stride, AV_PIX_FMT_BGRA, scalingAlgorithm)) + { + delete[] buffer; + result = NULL; + result_size = 0; + return false; + } + + bool success = GetThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height, stride, + image, result, result_size); + delete[] buffer; + + if (!success) + { + result = NULL; + result_size = 0; + } + + return success; +} + +bool CPicture::CacheTexture(CTexture* texture, + uint32_t& dest_width, + uint32_t& dest_height, + const std::string& dest, + CPictureScalingAlgorithm::Algorithm + scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */) +{ + return CacheTexture(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(), + texture->GetOrientation(), dest_width, dest_height, dest, scalingAlgorithm); +} + +bool CPicture::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 /* = CPictureScalingAlgorithm::NoAlgorithm */) +{ + const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + + // if no max width or height is specified, don't resize + if (dest_width == 0) + dest_width = width; + if (dest_height == 0) + dest_height = height; + if (scalingAlgorithm == CPictureScalingAlgorithm::NoAlgorithm) + scalingAlgorithm = advancedSettings->m_imageScalingAlgorithm; + + uint32_t max_height = advancedSettings->m_imageRes; + if (advancedSettings->m_fanartRes > advancedSettings->m_imageRes) + { // 16x9 images larger than the fanart res use that rather than the image res + if (fabsf(static_cast<float>(width) / static_cast<float>(height) / (16.0f / 9.0f) - 1.0f) + <= 0.01f) + { + max_height = advancedSettings->m_fanartRes; // use height defined in fanartRes + } + } + + uint32_t max_width = max_height * 16/9; + + dest_height = std::min(dest_height, max_height); + dest_width = std::min(dest_width, max_width); + + if (width > dest_width || height > dest_height || orientation) + { + bool success = false; + + dest_width = std::min(width, dest_width); + dest_height = std::min(height, dest_height); + + // create a buffer large enough for the resulting image + GetScale(width, height, dest_width, dest_height); + + // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top + // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157 + uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f); + uint32_t stride = dest_width_aligned * sizeof(uint32_t); + + uint32_t* buffer = new uint32_t[dest_width_aligned * dest_height + 4]; + if (buffer) + { + if (ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA, (uint8_t*)buffer, dest_width, + dest_height, stride, AV_PIX_FMT_BGRA, scalingAlgorithm)) + { + if (!orientation || + OrientateImage(buffer, dest_width, dest_height, orientation, dest_width_aligned)) + { + success = CreateThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height, + dest_width_aligned * 4, dest); + } + } + delete[] buffer; + } + return success; + } + else + { // no orientation needed + dest_width = width; + dest_height = height; + return CreateThumbnailFromSurface(pixels, width, height, pitch, dest); + } + return false; +} + +bool CPicture::CreateTiledThumb(const std::vector<std::string> &files, const std::string &thumb) +{ + if (!files.size()) + return false; + + unsigned int num_across = (unsigned int)ceil(sqrt((float)files.size())); + unsigned int num_down = (files.size() + num_across - 1) / num_across; + + unsigned int imageRes = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes; + + unsigned int tile_width = imageRes / num_across; + unsigned int tile_height = imageRes / num_down; + unsigned int tile_gap = 1; + bool success = false; + + // create a buffer for the resulting thumb + uint32_t *buffer = static_cast<uint32_t *>(calloc(imageRes * imageRes, 4)); + if (!buffer) + return false; + for (unsigned int i = 0; i < files.size(); ++i) + { + int x = i % num_across; + int y = i / num_across; + // load in the image + unsigned int width = tile_width - 2*tile_gap, height = tile_height - 2*tile_gap; + std::unique_ptr<CTexture> texture = CTexture::LoadFromFile(files[i], width, height, true); + if (texture && texture->GetWidth() && texture->GetHeight()) + { + GetScale(texture->GetWidth(), texture->GetHeight(), width, height); + + // scale appropriately + uint32_t *scaled = new uint32_t[width * height]; + if (ScaleImage(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), + texture->GetPitch(), AV_PIX_FMT_BGRA, (uint8_t*)scaled, width, height, + width * 4, AV_PIX_FMT_BGRA)) + { + unsigned int stridePixels{width}; + if (!texture->GetOrientation() || + OrientateImage(scaled, width, height, texture->GetOrientation(), stridePixels)) + { + success = true; // Flag that we at least had one successful image processed + // drop into the texture + unsigned int posX = x*tile_width + (tile_width - width)/2; + unsigned int posY = y*tile_height + (tile_height - height)/2; + uint32_t *dest = buffer + posX + posY * imageRes; + uint32_t *src = scaled; + for (unsigned int y = 0; y < height; ++y) + { + memcpy(dest, src, width*4); + dest += imageRes; + src += stridePixels; + } + } + } + delete[] scaled; + } + } + // now save to a file + if (success) + success = CreateThumbnailFromSurface((uint8_t *)buffer, imageRes, imageRes, imageRes * 4, thumb); + + free(buffer); + return success; +} + +void CPicture::GetScale(unsigned int width, unsigned int height, unsigned int &out_width, unsigned int &out_height) +{ + float aspect = (float)width / height; + if ((unsigned int)(out_width / aspect + 0.5f) > out_height) + out_width = (unsigned int)(out_height * aspect + 0.5f); + else + out_height = (unsigned int)(out_width / aspect + 0.5f); +} + +bool CPicture::ScaleImage(uint8_t* in_pixels, + unsigned int in_width, + unsigned int in_height, + unsigned int in_pitch, + AVPixelFormat in_format, + uint8_t* out_pixels, + unsigned int out_width, + unsigned int out_height, + unsigned int out_pitch, + AVPixelFormat out_format, + CPictureScalingAlgorithm::Algorithm + scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */) +{ + struct SwsContext* context = + sws_getContext(in_width, in_height, in_format, out_width, out_height, out_format, + CPictureScalingAlgorithm::ToSwscale(scalingAlgorithm), NULL, NULL, NULL); + + uint8_t *src[] = { in_pixels, 0, 0, 0 }; + int srcStride[] = { (int)in_pitch, 0, 0, 0 }; + uint8_t *dst[] = { out_pixels , 0, 0, 0 }; + int dstStride[] = { (int)out_pitch, 0, 0, 0 }; + + if (context) + { + sws_scale(context, src, srcStride, 0, in_height, dst, dstStride); + sws_freeContext(context); + return true; + } + return false; +} + +bool CPicture::OrientateImage(uint32_t*& pixels, + unsigned int& width, + unsigned int& height, + int orientation, + unsigned int& stridePixels) +{ + // ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c + bool out = false; + switch (orientation) + { + case 1: + out = FlipHorizontal(pixels, width, height, stridePixels); + break; + case 2: + out = Rotate180CCW(pixels, width, height, stridePixels); + break; + case 3: + out = FlipVertical(pixels, width, height, stridePixels); + break; + case 4: + out = Transpose(pixels, width, height, stridePixels); + break; + case 5: + out = Rotate270CCW(pixels, width, height, stridePixels); + break; + case 6: + out = TransposeOffAxis(pixels, width, height, stridePixels); + break; + case 7: + out = Rotate90CCW(pixels, width, height, stridePixels); + break; + default: + CLog::Log(LOGERROR, "Unknown orientation {}", orientation); + break; + } + return out; +} + +bool CPicture::FlipHorizontal(uint32_t*& pixels, + const unsigned int& width, + const unsigned int& height, + const unsigned int& stridePixels) +{ + // this can be done in-place easily enough + for (unsigned int y = 0; y < height; ++y) + { + uint32_t* line = pixels + y * stridePixels; + for (unsigned int x = 0; x < width / 2; ++x) + std::swap(line[x], line[width - 1 - x]); + } + return true; +} + +bool CPicture::FlipVertical(uint32_t*& pixels, + const unsigned int& width, + const unsigned int& height, + const unsigned int& stridePixels) +{ + // this can be done in-place easily enough + for (unsigned int y = 0; y < height / 2; ++y) + { + uint32_t* line1 = pixels + y * stridePixels; + uint32_t* line2 = pixels + (height - 1 - y) * stridePixels; + for (unsigned int x = 0; x < width; ++x) + std::swap(*line1++, *line2++); + } + return true; +} + +bool CPicture::Rotate180CCW(uint32_t*& pixels, + const unsigned int& width, + const unsigned int& height, + const unsigned int& stridePixels) +{ + // this can be done in-place easily enough + for (unsigned int y = 0; y < height / 2; ++y) + { + uint32_t* line1 = pixels + y * stridePixels; + uint32_t* line2 = pixels + (height - 1 - y) * stridePixels + width - 1; + for (unsigned int x = 0; x < width; ++x) + std::swap(*line1++, *line2--); + } + if (height % 2) + { // height is odd, so flip the middle row as well + uint32_t* line = pixels + (height - 1) / 2 * stridePixels; + for (unsigned int x = 0; x < width / 2; ++x) + std::swap(line[x], line[width - 1 - x]); + } + return true; +} + +bool CPicture::Rotate90CCW(uint32_t*& pixels, + unsigned int& width, + unsigned int& height, + unsigned int& stridePixels) +{ + uint32_t *dest = new uint32_t[width * height * 4]; + if (dest) + { + unsigned int d_height = width, d_width = height; + for (unsigned int y = 0; y < d_height; y++) + { + const uint32_t *src = pixels + (d_height - 1 - y); // y-th col from right, starting at top + uint32_t *dst = dest + d_width * y; // y-th row from top, starting at left + for (unsigned int x = 0; x < d_width; x++) + { + *dst++ = *src; + src += stridePixels; + } + } + delete[] pixels; + pixels = dest; + std::swap(width, height); + stridePixels = width; + return true; + } + return false; +} + +bool CPicture::Rotate270CCW(uint32_t*& pixels, + unsigned int& width, + unsigned int& height, + unsigned int& stridePixels) +{ + uint32_t *dest = new uint32_t[width * height * 4]; + if (!dest) + return false; + + unsigned int d_height = width, d_width = height; + for (unsigned int y = 0; y < d_height; y++) + { + const uint32_t* src = + pixels + stridePixels * (d_width - 1) + y; // y-th col from left, starting at bottom + uint32_t* dst = dest + d_width * y; // y-th row from top, starting at left + for (unsigned int x = 0; x < d_width; x++) + { + *dst++ = *src; + src -= stridePixels; + } + } + + delete[] pixels; + pixels = dest; + std::swap(width, height); + stridePixels = width; + return true; +} + +bool CPicture::Transpose(uint32_t*& pixels, + unsigned int& width, + unsigned int& height, + unsigned int& stridePixels) +{ + uint32_t *dest = new uint32_t[width * height * 4]; + if (!dest) + return false; + + unsigned int d_height = width, d_width = height; + for (unsigned int y = 0; y < d_height; y++) + { + const uint32_t *src = pixels + y; // y-th col from left, starting at top + uint32_t *dst = dest + d_width * y; // y-th row from top, starting at left + for (unsigned int x = 0; x < d_width; x++) + { + *dst++ = *src; + src += stridePixels; + } + } + + delete[] pixels; + pixels = dest; + std::swap(width, height); + stridePixels = width; + return true; +} + +bool CPicture::TransposeOffAxis(uint32_t*& pixels, + unsigned int& width, + unsigned int& height, + unsigned int& stridePixels) +{ + uint32_t *dest = new uint32_t[width * height * 4]; + if (!dest) + return false; + + unsigned int d_height = width, d_width = height; + for (unsigned int y = 0; y < d_height; y++) + { + const uint32_t* src = pixels + stridePixels * (d_width - 1) + + (d_height - 1 - y); // y-th col from right, starting at bottom + uint32_t *dst = dest + d_width * y; // y-th row, starting at left + for (unsigned int x = 0; x < d_width; x++) + { + *dst++ = *src; + src -= stridePixels; + } + } + + delete[] pixels; + pixels = dest; + std::swap(width, height); + stridePixels = width; + return true; +} |