spacebox/src/Texture.cpp
Cocktail Frank 6965d7255f Improve sb::Text blending
Update sb::Text blending so that no blending is used when the
background is fully transparent, leaving the text surface intact.

Fix a bug in Progress::stat_default so that it calculates a sum stat
instead of just skipping it.

Add overload to Color::percent for setting the color with a 3- or
4-dimensional vector.

Add hinting and alignment parameters to Game::font.

Add overload to Sprite::translate for setting translation with a 2D
vector.

Add range-based for loop to stats and achievements lists.
2024-12-12 13:33:07 -05:00

265 lines
8.0 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* +-------------------------------------------------------+
____/ \____ /| Open source game framework licensed to freely use, |
\ / / | copy, and modify, created for dank.game |
+--\ ^__^ /--+ | |
| ~/ \~ | | Download at https://open.shampoo.ooo/shampoo/spacebox |
| ~~~~~~~~~~~~ | +-------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#include "Texture.hpp"
using namespace sb;
Texture::Texture() : GLObject(texture_deleter) {}
Texture::Texture(fs::path path) : Texture()
{
associate(path);
}
void Texture::associate(fs::path path)
{
this->path = path;
}
void Texture::filter(GLint value)
{
_filter = value;
}
void Texture::generate()
{
GLObject::generate(glGenTextures);
}
void Texture::generate(glm::vec2 size, GLenum format, std::optional<GLint> filter_value)
{
/* Only generate a new texture ID and reallocate memory if the current texture ID hasn't been registered by this object as having
* identically sized memory with the same format. */
if (!generated() || !_size.has_value() || !_format.has_value() || _size != size || _format != format)
{
generate();
bind();
/* Use nullptr because data will be loaded later with glTexSubImage2d */
glTexImage2D(GL_TEXTURE_2D, 0, format, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
else
{
bind();
}
/* Set the resizing algorithm of this texture */
if (!filter_value.has_value())
{
filter_value = _filter;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_value.value());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_value.value());
/* Set the texture wrap of this texture */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
sb::Log::gl_errors("after generating texture");
/* Store a copy of size */
_size = size;
}
void Texture::load()
{
if (!path.empty())
{
#if !defined(__ANDROID__)
/* Can't check for file existence in an Android APK */
if (fs::exists(path))
{
#endif
load(path);
#if !defined(__ANDROID__)
}
else
{
std::ostringstream message;
message << "Error loading texture path: " << path;
sb::Log::log(message, sb::Log::ERR);
}
#endif
}
else
{
std::ostringstream message;
message << "Cannot load, no path has been specified yet for texture ID " << id();
sb::Log::log(message, sb::Log::WARN);
}
}
void Texture::load(fs::path path)
{
std::ostringstream message;
sb::Log::Level message_level;
if (path.has_filename())
{
this->associate(path);
/* Load file path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will free
* itself when it goes out of scope at the end of this function. */
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load(path.string().c_str()), SDL_FreeSurface);
if (surface.get() != nullptr)
{
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
if (flipped_surface.get() != nullptr)
{
load(flipped_surface.get());
message << "Loading image at " << path;
message_level = sb::Log::INFO;
}
else
{
message << "Error flipping image surface at " << path;
throw std::runtime_error(sb::Log::sdl_error(message.str()).str());
}
}
else
{
message << "Error opening image " << path;
throw std::runtime_error(sb::Log::sdl_error(message.str()).str());
}
}
else
{
message << "Cannot load, " << path << " is not a vaild path to a file";
message_level = sb::Log::WARN;
}
sb::Log::log(message, message_level);
}
void Texture::load(SDL_RWops* rw)
{
/* Load RW object as path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will
* free itself when it goes out of scope at the end of this function. */
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load_RW(rw, 0), SDL_FreeSurface);
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
load(flipped_surface.get());
}
void Texture::load(SDL_Surface* surface)
{
std::ostringstream message;
if (surface->w > 0 && surface->h > 0)
{
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " <<
SDL_GetPixelFormatName(surface->format->format) << ")";
sb::Log::log(message, sb::Log::VERBOSE);
#if defined(__APPLE__)
load(surface->pixels, {surface->w, surface->h}, GL_BGRA, GL_UNSIGNED_BYTE);
#else
load(surface->pixels, {surface->w, surface->h}, GL_RGBA, GL_UNSIGNED_BYTE);
#endif
}
else
{
message << "Cannot load into texture, invalid image data without dimensions found";
sb::Log::log(message, sb::Log::WARN);
}
}
void Texture::load(void* pixels, glm::vec2 size, GLenum format, GLenum type)
{
std::ostringstream message;
sb::Log::Level message_level;
if (size.x > 0 && size.y > 0)
{
if (!generated())
{
generate(size);
}
bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, format, type, pixels);
message << "Loaded " << size.x << "×" << size.y << " image into texture ID " << id();
message_level = sb::Log::VERBOSE;
}
else
{
message << "Cannot load pixels with zero or negative size in either dimension.";
message_level = sb::Log::WARN;
}
sb::Log::log(message, message_level);
sb::Log::gl_errors("after loading texture");
}
void Texture::bind() const
{
if (generated())
{
glBindTexture(GL_TEXTURE_2D, this->id());
}
else
{
std::ostringstream message;
message << "Cannot bind texture that has not been generated yet at ID " << this->id() << ".";
if (!path.empty())
{
message << " Texture path is " << path << ".";
}
throw std::runtime_error(message.str());
}
}
void Texture::load(void* pixels, GLenum format, GLenum type)
{
if (generated())
{
load(pixels, size(), format, type);
}
else
{
std::ostringstream message;
message << "Texture";
if (!path.empty())
{
message << " with path " << path;
}
message << " has not been generated and has no size property, so a size must be provided to the load function.";
throw std::invalid_argument(message.str());
}
}
glm::vec2 Texture::size() const
{
if (_size.has_value())
{
return *_size;
}
else
{
std::ostringstream message;
message << "Texture";
if (!path.empty())
{
message << " with path " << path;
}
message << " has not been generated or loaded with a size given, so it currently has no size.";
throw std::runtime_error(message.str());
}
}
bool Texture::operator==(const Texture& texture) const
{
return id() == texture.id();
}
/* This function gets passed to the abstract base class for deleting the texture data when the ID
* pointer goes out of scope (when all instances of this texture and its copies are out of scope) */
void sb::texture_deleter(GLuint* id)
{
/* not sure why SDL_Log works here at program end but SDL_LogDebug and SDL_LogInfo don't */
SDL_LogVerbose(sb::Log::DEFAULT_CATEGORY, "destroying texture ID %i", *id);
glDeleteTextures(1, id);
delete id;
}