clooneljump/src/texture.cpp

403 lines
13 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#include "texture.hpp"
#include "sdlerror.hpp"
#include "sdlmain.hpp"
#include "physicsfswrapper.hpp"
#include "compatibility.h"
#include "casts.hpp"
#include <SDL2/SDL.h>
#include <stdexcept>
#include <cassert>
#include <ciso646>
#include <png.h>
#include <endian.h>
#include <vector>
#if !defined(NDEBUG)
# include <cmath>
#endif
#define lengthof(a) (static_cast<int32_t>(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<float>(parRect.x), static_cast<float>(parRect.y)),
to(static_cast<float>(parRect.x + parRect.w), static_cast<float>(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<int>(from.x()), static_cast<int>(from.y()), static_cast<int>(Width()), static_cast<int>(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<uint8_t>& 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<int>(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<PhysicsFSFile*>(png_get_io_ptr(parPngPtr));
const int64_t read = rawfile.Read(static_cast<void*>(parOutBytes), checked_numcast<uint32_t>(parByteCountToRead), 1);
if (read != static_cast<int64_t>(parByteCountToRead))
return;
return;
}
///----------------------------------------------------------------------
///http://blog.hammerian.net/2009/reading-png-images-from-memory/
///----------------------------------------------------------------------
SDL_Surface* LoadNewPngSurface (const std::string& parPath, std::vector<uint8_t>& 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<png_uint_32>(-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<uint8_t>(width), static_cast<uint8_t>(height)), pngptr, infoptr, parBuff);
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
assert(bitDepth * 4 == 32);
retSurf = SurfaceFromPng(PixelFormat_RGBA_32, ushort2(static_cast<uint8_t>(width), static_cast<uint8_t>(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<uint8_t> 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<float2>(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<float2>(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<float2>(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<int>(m_size.x()));
assert(sdlsrc.h >= 0 and sdlsrc.h <= static_cast<int>(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