/* Copyright 2014 Michele "King_DuckZ" Santullo This file is part of CloonelJump. CloonelJump is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CloonelJump is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with CloonelJump. If not, see . */ #include "texture.hpp" #include "sdlerror.hpp" #include "sdlmain.hpp" #include "physicsfswrapper.hpp" #include "compatibility.h" #include "casts.hpp" #include #include #include #include #include #include #include #if !defined(NDEBUG) # include #endif #define lengthof(a) (static_cast(sizeof(a) / sizeof(a[0]))) namespace cloonel { namespace { enum GraphicFormat { GraphicFormat_Unknown, GraphicFormat_Png }; enum PixelFormat { PixelFormat_RGB_24, PixelFormat_RGBA_32 }; enum ColorChannelMask { #if __BYTE_ORDER == __BIG_ENDIAN ColorChannelMask_Red = 0xff000000, ColorChannelMask_Green = 0xff0000, ColorChannelMask_Blue = 0xff00, ColorChannelMask_Alpha = 0xff #elif __BYTE_ORDER == __LITTLE_ENDIAN ColorChannelMask_Red = 0xff, ColorChannelMask_Green = 0xff00, ColorChannelMask_Blue = 0xff0000, ColorChannelMask_Alpha = 0xff000000 #else # error "Unknonwn endianness" #endif }; struct GraphicFormatItem { const char* extension; size_t length; GraphicFormat enumvalue; }; struct RectFloat { float2 from; float2 to; RectFloat ( void ) = default; RectFloat ( const RectFloat& parOther ) : from(parOther.from), to(parOther.to) { assert(IsValid()); } RectFloat ( const SDL_Rect& parRect ) : from(static_cast(parRect.x), static_cast(parRect.y)), to(static_cast(parRect.x + parRect.w), static_cast(parRect.y + parRect.h)) { assert(IsValid()); } RectFloat ( const float2& parFrom, const float2& parTo ) : from(parFrom), to(parTo) { assert(IsValid()); } ~RectFloat ( void ) noexcept = default; operator SDL_Rect ( void ) const noexcept { return { static_cast(from.x()), static_cast(from.y()), static_cast(Width()), static_cast(Height()) }; } float Width ( void ) const noexcept { return to.x() - from.x(); } float Height ( void ) const noexcept { return to.y() - from.y(); } float2 WidthHeight ( void ) const noexcept { return float2(Width(), Height()); } bool IsValid ( void ) const noexcept { return from <= to; } }; const GraphicFormatItem g_graphicFormatItems[] = { {".png", 4, GraphicFormat_Png} }; GraphicFormat GuessGraphicFormatFromName (const std::string& parPath) a_pure; bool ClipRect ( RectFloat& parSrc, RectFloat& parDest, const RectFloat& parClip ); #if !defined(NDEBUG) bool IsRectCompletelyInsideRect ( const RectFloat& parInner, const RectFloat& parOuter ) a_pure; #endif ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- SDL_Surface* SurfaceFromPng (PixelFormat parPixelFormat, ushort2 parSize, const png_structp& parPngPtr, const png_infop& parInfoPtr, std::vector& parBuff) { const png_size_t stride = png_get_rowbytes(parPngPtr, parInfoPtr); assert(stride > 0); parBuff.resize(stride * parSize.y()); uint8_t* const imagePtr = parBuff.data(); for (uint16_t y = 0; y < parSize.y(); ++y) { png_read_row(parPngPtr, imagePtr + stride * y, nullptr); } int bpp; uint32_t redMask, greenMask, blueMask, alphaMask; switch (parPixelFormat) { case PixelFormat_RGB_24: bpp = 24; redMask = ColorChannelMask_Red; greenMask = ColorChannelMask_Green; blueMask = ColorChannelMask_Blue; alphaMask = 0; break; case PixelFormat_RGBA_32: bpp = 32; redMask = ColorChannelMask_Red; greenMask = ColorChannelMask_Green; blueMask = ColorChannelMask_Blue; alphaMask = ColorChannelMask_Alpha; break; default: assert(false); } SDL_Surface* const retSurf = SDL_CreateRGBSurfaceFrom( imagePtr, parSize.x(), parSize.y(), bpp, checked_numcast(stride), redMask, greenMask, blueMask, alphaMask ); return retSurf; } ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- GraphicFormat GuessGraphicFormatFromName (const std::string& parPath) { const size_t dotPos = parPath.find_last_of('.'); if (parPath.npos == dotPos) { return GraphicFormat_Unknown; } const size_t extLen = parPath.size() - dotPos; for (size_t z = 0; z < sizeof(g_graphicFormatItems) / sizeof(g_graphicFormatItems[0]); ++z) { const GraphicFormatItem& currItem = g_graphicFormatItems[z]; if (currItem.length == extLen and parPath.compare(dotPos, currItem.length, currItem.extension) == 0) { return currItem.enumvalue; } } return GraphicFormat_Unknown; } ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- void ReadDataFromInputStream (png_structp parPngPtr, png_bytep parOutBytes, png_size_t parByteCountToRead) { if (not png_get_io_ptr(parPngPtr)) return; PhysicsFSFile& rawfile = *static_cast(png_get_io_ptr(parPngPtr)); const int64_t read = rawfile.Read(static_cast(parOutBytes), checked_numcast(parByteCountToRead), 1); if (read != static_cast(parByteCountToRead)) return; return; } ///---------------------------------------------------------------------- ///http://blog.hammerian.net/2009/reading-png-images-from-memory/ ///---------------------------------------------------------------------- SDL_Surface* LoadNewPngSurface (const std::string& parPath, std::vector& parBuff) { PhysicsFSFile rawfile(parPath.c_str(), PhysicsFSFile::OpenMode_Read, "graphics"); unsigned char header[8]; assert(rawfile.IsOpen()); //Read the signature from the input stream rawfile.Read(header, lengthof(header), 1); if (png_sig_cmp(header, 0, lengthof(header))) return nullptr; //Get the file info struct png_structp pngptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (not pngptr) return nullptr; //Get the data info struct png_infop infoptr = png_create_info_struct(pngptr); if (not infoptr) { png_destroy_read_struct(&pngptr, nullptr, nullptr); return nullptr; } //Set the function that will be called to read data from the stream png_set_read_fn(pngptr, &rawfile, ReadDataFromInputStream); //Tell the lib we already verified the signature png_set_sig_bytes(pngptr, lengthof(header)); //Read the header png_uint_32 width, height; int bitDepth, colorType; png_read_info(pngptr, infoptr); const png_uint_32 headerGetInfoRetVal = png_get_IHDR(pngptr, infoptr, &width, &height, &bitDepth, &colorType, nullptr, nullptr, nullptr); if (static_cast(-1) == headerGetInfoRetVal) { png_destroy_read_struct(&pngptr, &infoptr, nullptr); return nullptr; } SDL_Surface* retSurf; switch (colorType) { case PNG_COLOR_TYPE_RGB: assert(3 * bitDepth == 24); retSurf = SurfaceFromPng(PixelFormat_RGB_24, ushort2(static_cast(width), static_cast(height)), pngptr, infoptr, parBuff); break; case PNG_COLOR_TYPE_RGB_ALPHA: assert(bitDepth * 4 == 32); retSurf = SurfaceFromPng(PixelFormat_RGBA_32, ushort2(static_cast(width), static_cast(height)), pngptr, infoptr, parBuff); break; default: retSurf = nullptr; } png_destroy_read_struct(&pngptr, &infoptr, nullptr); return retSurf; } ///---------------------------------------------------------------------- ///Adjustes parSrc and parDest so that parDest will fit into parClip and ///parSrc to be the relevant part of the source texture to be drawn. ///Returns true if such operation was possible and parSrc and parDest ///got set to a valid value, otherwise false. ///---------------------------------------------------------------------- bool ClipRect (RectFloat& parSrc, RectFloat& parDest, const RectFloat& parClip) { assert(parSrc.IsValid()); assert(parDest.IsValid()); //If the dest rect is completely out of the clipping region, there //is nothing to do at all. if (parDest.to <= parClip.from or parDest.from >= parClip.to) return false; RectFloat dst; dst.from.x() = std::max(parDest.from.x(), parClip.from.x()); dst.from.y() = std::max(parDest.from.y(), parClip.from.y()); dst.to.x() = std::min(parDest.to.x(), parClip.to.x()); dst.to.y() = std::min(parDest.to.y(), parClip.to.y()); if (not dst.IsValid()) return false; assert(parDest.from <= dst.from); assert(parDest.to >= dst.to); RectFloat src; { const float2 srcWidthHeight(parSrc.WidthHeight()); const float2 scaledOffs((dst.from - parDest.from) / parDest.WidthHeight()); src.from = parSrc.from + scaledOffs * srcWidthHeight; const float2 scaledCrop((parDest.to - dst.to) / parDest.WidthHeight()); src.to = parSrc.to - scaledCrop * srcWidthHeight; assert(src.IsValid()); assert(dst.IsValid()); assert(dst.from >= parClip.from); assert(dst.to <= parClip.to); } parDest = dst; parSrc = src; return true; } #if !defined(NDEBUG) ///---------------------------------------------------------------------- ///---------------------------------------------------------------------- bool IsRectCompletelyInsideRect (const RectFloat& parInner, const RectFloat& parOuter) { return parInner.from >= parOuter.from and parInner.to <= parOuter.to; } #endif } //unnamed namespace ///-------------------------------------------------------------------------- ///-------------------------------------------------------------------------- Texture::Texture (const std::string& parPath, SDLMain* parMain, bool parLoadNow) : m_path(parPath), m_texture(nullptr), m_sdlmain(parMain) { if (parLoadNow) Reload(); } ///-------------------------------------------------------------------------- ///-------------------------------------------------------------------------- Texture::~Texture() noexcept { Destroy(); } ///-------------------------------------------------------------------------- ///-------------------------------------------------------------------------- void Texture::Destroy() noexcept { if (m_texture) { SDL_DestroyTexture(m_texture); m_texture = nullptr; } } ///-------------------------------------------------------------------------- ///-------------------------------------------------------------------------- void Texture::Reload() { const GraphicFormat fmt = GuessGraphicFormatFromName(m_path); Destroy(); SDL_Surface* surf = nullptr; std::vector memBuff; switch (fmt) { case GraphicFormat_Png: surf = LoadNewPngSurface(m_path.c_str(), memBuff); break; default: throw std::runtime_error(std::string("Unsupported file format for \"") + m_path + "\""); } if (nullptr == surf) throw std::runtime_error(GetFullErrorMessage(__PRETTY_FUNCTION__, m_path)); m_texture = SDL_CreateTextureFromSurface(m_sdlmain->GetRenderer(), surf); SDL_FreeSurface(surf); if (m_texture) { int2 wh; SDL_QueryTexture(m_texture, nullptr, nullptr, &wh.x(), &wh.y()); m_size = vector_cast(wh); } } ///-------------------------------------------------------------------------- ///-------------------------------------------------------------------------- void Texture::Render (const float2& parPos, const float2& parSize, const float2& parScaling, bool parClip) const { assert(IsLoaded()); const float2 pos(parPos * parScaling); const float2 siz(parSize * parScaling); RectFloat dest(pos, pos + siz); RectFloat src(float2(0.0f), m_size); if (parClip) { const RectFloat clip(float2(0.0f), vector_cast(m_sdlmain->WidthHeight())); const bool visible = ClipRect(src, dest, clip); if (not visible) return; } #if !defined(NDEBUG) else { const RectFloat clip(float2(0.0f), vector_cast(m_sdlmain->WidthHeight())); assert(IsRectCompletelyInsideRect(dest, clip)); } #endif const SDL_Rect sdlsrc(src); SDL_Rect sdldst(dest); sdldst.y = m_sdlmain->WidthHeight().y() - sdldst.y - sdldst.h; //By here everything is nice and clean assert(sdlsrc.x >= 0 and sdlsrc.y >= 0); assert(sdlsrc.w >= 0 and sdlsrc.w <= static_cast(m_size.x())); assert(sdlsrc.h >= 0 and sdlsrc.h <= static_cast(m_size.y())); assert(sdlsrc.x + sdlsrc.w <= m_sdlmain->WidthHeight().x()); assert(sdlsrc.y + sdlsrc.h <= m_sdlmain->WidthHeight().y()); SDL_RenderCopy(m_sdlmain->GetRenderer(), m_texture, &sdlsrc, &sdldst); } } //namespace cloonel