summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/Texture.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/Texture.cpp')
-rw-r--r--xbmc/guilib/Texture.cpp495
1 files changed, 495 insertions, 0 deletions
diff --git a/xbmc/guilib/Texture.cpp b/xbmc/guilib/Texture.cpp
new file mode 100644
index 0000000..4a2f80e
--- /dev/null
+++ b/xbmc/guilib/Texture.cpp
@@ -0,0 +1,495 @@
+/*
+ * 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 "Texture.h"
+
+#include "DDSImage.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "commons/ilog.h"
+#include "filesystem/File.h"
+#include "filesystem/ResourceFile.h"
+#include "filesystem/XbtFile.h"
+#include "guilib/iimage.h"
+#include "guilib/imagefactory.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+#if defined(TARGET_DARWIN_EMBEDDED)
+#include <ImageIO/ImageIO.h>
+#include "filesystem/File.h"
+#endif
+#if defined(TARGET_ANDROID)
+#include "platform/android/filesystem/AndroidAppFile.h"
+#endif
+#include "rendering/RenderSystem.h"
+#include "utils/MemUtils.h"
+
+#include <algorithm>
+#include <cstring>
+#include <exception>
+#include <utility>
+
+/************************************************************************/
+/* */
+/************************************************************************/
+CTexture::CTexture(unsigned int width, unsigned int height, unsigned int format)
+{
+ m_pixels = NULL;
+ m_loadedToGPU = false;
+ Allocate(width, height, format);
+}
+
+CTexture::~CTexture()
+{
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = NULL;
+}
+
+void CTexture::Allocate(unsigned int width, unsigned int height, unsigned int format)
+{
+ m_imageWidth = m_originalWidth = width;
+ m_imageHeight = m_originalHeight = height;
+ m_format = format;
+ m_orientation = 0;
+
+ m_textureWidth = m_imageWidth;
+ m_textureHeight = m_imageHeight;
+
+ if (m_format & XB_FMT_DXT_MASK)
+ {
+ while (GetPitch() < CServiceBroker::GetRenderSystem()->GetMinDXTPitch())
+ m_textureWidth += GetBlockSize();
+ }
+
+ if (!CServiceBroker::GetRenderSystem()->SupportsNPOT((m_format & XB_FMT_DXT_MASK) != 0))
+ {
+ m_textureWidth = PadPow2(m_textureWidth);
+ m_textureHeight = PadPow2(m_textureHeight);
+ }
+
+ if (m_format & XB_FMT_DXT_MASK)
+ {
+ // DXT textures must be a multiple of 4 in width and height
+ m_textureWidth = ((m_textureWidth + 3) / 4) * 4;
+ m_textureHeight = ((m_textureHeight + 3) / 4) * 4;
+ }
+ else
+ {
+ // align all textures so that they have an even width
+ // in some circumstances when we downsize a thumbnail
+ // which has an uneven number of pixels in width
+ // we crash in CPicture::ScaleImage in ffmpegs swscale
+ // because it tries to access beyond the source memory
+ // (happens on osx and ios)
+ // UPDATE: don't just update to be on an even width;
+ // ffmpegs swscale relies on a 16-byte stride on some systems
+ // so the textureWidth needs to be a multiple of 16. see ffmpeg
+ // swscale headers for more info.
+ m_textureWidth = ((m_textureWidth + 15) / 16) * 16;
+ }
+
+ // check for max texture size
+ #define CLAMP(x, y) { if (x > y) x = y; }
+ CLAMP(m_textureWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize());
+ CLAMP(m_textureHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize());
+ CLAMP(m_imageWidth, m_textureWidth);
+ CLAMP(m_imageHeight, m_textureHeight);
+
+ KODI::MEMORY::AlignedFree(m_pixels);
+ m_pixels = NULL;
+ if (GetPitch() * GetRows() > 0)
+ {
+ size_t size = GetPitch() * GetRows();
+ m_pixels = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(size, 32));
+
+ if (m_pixels == nullptr)
+ {
+ CLog::Log(LOGERROR, "{} - Could not allocate {} bytes. Out of memory.", __FUNCTION__, size);
+ }
+ }
+}
+
+void CTexture::Update(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ const unsigned char* pixels,
+ bool loadToGPU)
+{
+ if (pixels == NULL)
+ return;
+
+ if (format & XB_FMT_DXT_MASK)
+ return;
+
+ Allocate(width, height, format);
+
+ if (m_pixels == nullptr)
+ return;
+
+ unsigned int srcPitch = pitch ? pitch : GetPitch(width);
+ unsigned int srcRows = GetRows(height);
+ unsigned int dstPitch = GetPitch(m_textureWidth);
+ unsigned int dstRows = GetRows(m_textureHeight);
+
+ if (srcPitch == dstPitch)
+ memcpy(m_pixels, pixels, srcPitch * std::min(srcRows, dstRows));
+ else
+ {
+ const unsigned char *src = pixels;
+ unsigned char* dst = m_pixels;
+ for (unsigned int y = 0; y < srcRows && y < dstRows; y++)
+ {
+ memcpy(dst, src, std::min(srcPitch, dstPitch));
+ src += srcPitch;
+ dst += dstPitch;
+ }
+ }
+ ClampToEdge();
+
+ if (loadToGPU)
+ LoadToGPU();
+}
+
+void CTexture::ClampToEdge()
+{
+ if (m_pixels == nullptr)
+ return;
+
+ unsigned int imagePitch = GetPitch(m_imageWidth);
+ unsigned int imageRows = GetRows(m_imageHeight);
+ unsigned int texturePitch = GetPitch(m_textureWidth);
+ unsigned int textureRows = GetRows(m_textureHeight);
+ if (imagePitch < texturePitch)
+ {
+ unsigned int blockSize = GetBlockSize();
+ unsigned char *src = m_pixels + imagePitch - blockSize;
+ unsigned char *dst = m_pixels;
+ for (unsigned int y = 0; y < imageRows; y++)
+ {
+ for (unsigned int x = imagePitch; x < texturePitch; x += blockSize)
+ memcpy(dst + x, src, blockSize);
+ dst += texturePitch;
+ }
+ }
+
+ if (imageRows < textureRows)
+ {
+ unsigned char *dst = m_pixels + imageRows * texturePitch;
+ for (unsigned int y = imageRows; y < textureRows; y++)
+ {
+ memcpy(dst, dst - texturePitch, texturePitch);
+ dst += texturePitch;
+ }
+ }
+}
+
+std::unique_ptr<CTexture> CTexture::LoadFromFile(const std::string& texturePath,
+ unsigned int idealWidth,
+ unsigned int idealHeight,
+ bool requirePixels,
+ const std::string& strMimeType)
+{
+#if defined(TARGET_ANDROID)
+ CURL url(texturePath);
+ if (url.IsProtocol("androidapp"))
+ {
+ XFILE::CFileAndroidApp file;
+ if (file.Open(url))
+ {
+ unsigned char* inputBuff;
+ unsigned int width;
+ unsigned int height;
+ unsigned int inputBuffSize = file.ReadIcon(&inputBuff, &width, &height);
+ file.Close();
+ if (!inputBuffSize)
+ return NULL;
+
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ texture->LoadFromMemory(width, height, width*4, XB_FMT_RGBA8, true, inputBuff);
+ delete[] inputBuff;
+ return texture;
+ }
+ }
+#endif
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ if (texture->LoadFromFileInternal(texturePath, idealWidth, idealHeight, requirePixels, strMimeType))
+ return texture;
+ return {};
+}
+
+std::unique_ptr<CTexture> CTexture::LoadFromFileInMemory(unsigned char* buffer,
+ size_t bufferSize,
+ const std::string& mimeType,
+ unsigned int idealWidth,
+ unsigned int idealHeight)
+{
+ std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
+ if (texture->LoadFromFileInMem(buffer, bufferSize, mimeType, idealWidth, idealHeight))
+ return texture;
+ return {};
+}
+
+bool CTexture::LoadFromFileInternal(const std::string& texturePath,
+ unsigned int maxWidth,
+ unsigned int maxHeight,
+ bool requirePixels,
+ const std::string& strMimeType)
+{
+ if (URIUtils::HasExtension(texturePath, ".dds"))
+ { // special case for DDS images
+ CDDSImage image;
+ if (image.ReadFile(texturePath))
+ {
+ Update(image.GetWidth(), image.GetHeight(), 0, image.GetFormat(), image.GetData(), false);
+ return true;
+ }
+ return false;
+ }
+
+ unsigned int width = maxWidth ? std::min(maxWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ unsigned int height = maxHeight ? std::min(maxHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+
+ // Read image into memory to use our vfs
+ XFILE::CFile file;
+ std::vector<uint8_t> buf;
+
+ if (file.LoadFile(texturePath, buf) <= 0)
+ return false;
+
+ CURL url(texturePath);
+ // make sure resource:// paths are properly resolved
+ if (url.IsProtocol("resource"))
+ {
+ std::string translatedPath;
+ if (XFILE::CResourceFile::TranslatePath(url, translatedPath))
+ url.Parse(translatedPath);
+ }
+
+ // handle xbt:// paths differently because it allows loading the texture directly from memory
+ if (url.IsProtocol("xbt"))
+ {
+ XFILE::CXbtFile xbtFile;
+ if (!xbtFile.Open(url))
+ return false;
+
+ return LoadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0,
+ xbtFile.GetImageFormat(), xbtFile.HasImageAlpha(), buf.data());
+ }
+
+ IImage* pImage;
+
+ if(strMimeType.empty())
+ pImage = ImageFactory::CreateLoader(texturePath);
+ else
+ pImage = ImageFactory::CreateLoaderFromMimeType(strMimeType);
+
+ if (!LoadIImage(pImage, buf.data(), buf.size(), width, height))
+ {
+ CLog::Log(LOGDEBUG, "{} - Load of {} failed.", __FUNCTION__, CURL::GetRedacted(texturePath));
+ delete pImage;
+ return false;
+ }
+ delete pImage;
+
+ return true;
+}
+
+bool CTexture::LoadFromFileInMem(unsigned char* buffer,
+ size_t size,
+ const std::string& mimeType,
+ unsigned int maxWidth,
+ unsigned int maxHeight)
+{
+ if (!buffer || !size)
+ return false;
+
+ unsigned int width = maxWidth ? std::min(maxWidth, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ unsigned int height = maxHeight ? std::min(maxHeight, CServiceBroker::GetRenderSystem()->GetMaxTextureSize()) :
+ CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+
+ IImage* pImage = ImageFactory::CreateLoaderFromMimeType(mimeType);
+ if(!LoadIImage(pImage, buffer, size, width, height))
+ {
+ delete pImage;
+ return false;
+ }
+ delete pImage;
+ return true;
+}
+
+bool CTexture::LoadIImage(IImage* pImage,
+ unsigned char* buffer,
+ unsigned int bufSize,
+ unsigned int width,
+ unsigned int height)
+{
+ if(pImage != NULL && pImage->LoadImageFromMemory(buffer, bufSize, width, height))
+ {
+ if (pImage->Width() > 0 && pImage->Height() > 0)
+ {
+ Allocate(pImage->Width(), pImage->Height(), XB_FMT_A8R8G8B8);
+ if (m_pixels != nullptr && pImage->Decode(m_pixels, GetTextureWidth(), GetRows(), GetPitch(), XB_FMT_A8R8G8B8))
+ {
+ if (pImage->Orientation())
+ m_orientation = pImage->Orientation() - 1;
+ m_hasAlpha = pImage->hasAlpha();
+ m_originalWidth = pImage->originalWidth();
+ m_originalHeight = pImage->originalHeight();
+ m_imageWidth = pImage->Width();
+ m_imageHeight = pImage->Height();
+ ClampToEdge();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool CTexture::LoadFromMemory(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ bool hasAlpha,
+ const unsigned char* pixels)
+{
+ m_imageWidth = m_originalWidth = width;
+ m_imageHeight = m_originalHeight = height;
+ m_format = format;
+ m_hasAlpha = hasAlpha;
+ Update(width, height, pitch, format, pixels, false);
+ return true;
+}
+
+bool CTexture::LoadPaletted(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int format,
+ const unsigned char* pixels,
+ const COLOR* palette)
+{
+ if (pixels == NULL || palette == NULL)
+ return false;
+
+ Allocate(width, height, format);
+
+ for (unsigned int y = 0; y < m_imageHeight; y++)
+ {
+ unsigned char *dest = m_pixels + y * GetPitch();
+ const unsigned char *src = pixels + y * pitch;
+ for (unsigned int x = 0; x < m_imageWidth; x++)
+ {
+ COLOR col = palette[*src++];
+ *dest++ = col.b;
+ *dest++ = col.g;
+ *dest++ = col.r;
+ *dest++ = col.x;
+ }
+ }
+ ClampToEdge();
+ return true;
+}
+
+unsigned int CTexture::PadPow2(unsigned int x)
+{
+ --x;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ return ++x;
+}
+
+bool CTexture::SwapBlueRed(unsigned char* pixels,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned int elements,
+ unsigned int offset)
+{
+ if (!pixels) return false;
+ unsigned char *dst = pixels;
+ for (unsigned int y = 0; y < height; y++)
+ {
+ dst = pixels + (y * pitch);
+ for (unsigned int x = 0; x < pitch; x+=elements)
+ std::swap(dst[x+offset], dst[x+2+offset]);
+ }
+ return true;
+}
+
+unsigned int CTexture::GetPitch(unsigned int width) const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return ((width + 3) / 4) * 8;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return ((width + 3) / 4) * 16;
+ case XB_FMT_A8:
+ return width;
+ case XB_FMT_RGB8:
+ return (((width + 1)* 3 / 4) * 4);
+ case XB_FMT_RGBA8:
+ case XB_FMT_A8R8G8B8:
+ default:
+ return width*4;
+ }
+}
+
+unsigned int CTexture::GetRows(unsigned int height) const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return (height + 3) / 4;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return (height + 3) / 4;
+ default:
+ return height;
+ }
+}
+
+unsigned int CTexture::GetBlockSize() const
+{
+ switch (m_format)
+ {
+ case XB_FMT_DXT1:
+ return 8;
+ case XB_FMT_DXT3:
+ case XB_FMT_DXT5:
+ case XB_FMT_DXT5_YCoCg:
+ return 16;
+ case XB_FMT_A8:
+ return 1;
+ default:
+ return 4;
+ }
+}
+
+bool CTexture::HasAlpha() const
+{
+ return m_hasAlpha;
+}
+
+void CTexture::SetMipmapping()
+{
+ m_mipmapping = true;
+}
+
+bool CTexture::IsMipmapped() const
+{
+ return m_mipmapping;
+}