added function for extracting a grid of tile surfaces from a larger SDL surface

This commit is contained in:
ohsqueezy 2023-09-18 22:10:04 -04:00
parent d882e111b3
commit 9a6b80f443
4 changed files with 113 additions and 4 deletions

View File

@ -403,8 +403,6 @@ void Box::center(const glm::vec2& center)
move(center - this->center());
}
/* Called when a Box instance is converted into an SDL_Rect. Allows passing Box to SDL functions that
* expect an SDL_Rect instead of an SDL_FRect */
Box::operator SDL_Rect() const
{
return {static_cast<int>(left()), static_cast<int>(top()), static_cast<int>(width()), static_cast<int>(height())};

View File

@ -135,7 +135,16 @@ public:
void sw(const glm::vec2&);
void west(const glm::vec2&);
void center(const glm::vec2&);
/*!
* Called when a Box instance is converted into an SDL_Rect. Allows passing Box to SDL functions that
* expect an SDL_Rect instead of an SDL_FRect. Corner values are converted from float to int using a static
* cast.
*
* @return box with all corners cast to int and converted to SDL_Rect
*/
operator SDL_Rect() const;
void clear();
/*!

View File

@ -707,6 +707,76 @@ fs::path sb::copy_file(fs::path from, fs::path to, bool overwrite_ok)
return destination;
}
std::shared_ptr<SDL_Surface> sb::extract_area(const std::shared_ptr<SDL_Surface>& source, const sb::Box& area)
{
/* Create a destination surface with the same format as the source and is the size of the tile. */
std::shared_ptr<SDL_Surface> destination {
SDL_CreateRGBSurfaceWithFormat(source->flags, area.w, area.h, source->format->BitsPerPixel, source->format->format), SDL_FreeSurface};
if (destination.get() != nullptr)
{
/* Blit the source onto the destination */
SDL_Rect rect = area;
if (SDL_BlitSurface(source.get(), &rect, destination.get(), nullptr) == 0)
{
/* Rotate and mirror the tile for compatibility with OpenGL */
std::shared_ptr<SDL_Surface> flipped {rotozoomSurfaceXY(destination.get(), 0, 1, -1, 0), SDL_FreeSurface};
if (flipped.get() != nullptr)
{
return flipped;
}
else
{
std::ostringstream message;
message << "Could not rotate source surface for tile extraction. " << SDL_GetError();
throw std::runtime_error(message.str());
}
}
else
{
std::ostringstream message;
message << "Could not blit source pixels to destination surface for tile extraction. " << SDL_GetError();
throw std::runtime_error(message.str());
}
}
else
{
std::ostringstream message;
message << "Could not create destination surface for tile extraction. " << SDL_GetError();
throw std::runtime_error(message.str());
}
}
std::vector<std::shared_ptr<SDL_Surface>> sb::extract_tiles_by_count(const std::shared_ptr<SDL_Surface>& source, const glm::ivec2& count)
{
glm::ivec2 source_size {source->w, source->h};
/* Get the floor of the division of the source size by the tile count. Then add one to each dimension that has any remainder. This leads
* to a tile size that may be bigger than the available pixels in the last tile in the dimension. In that case, the last tile will contain
* the rest of the available pixels and will be smaller than the tile size. */
glm::ivec2 tile_size {source_size / count + glm::ivec2(glm::bvec2(glm::mod(glm::fvec2(source_size), glm::fvec2(count))))};
/* Use SDL coordinate system for the area box, meaning at the top of the image y=0, and at the bottom y=height. The Box class uses GL
* coordinates by default, but for image manipulation and SDL surfaces, the Box class supports flipping the Y-axis to use SDL coordinates. */
sb::Box area {{0.0f, 0.0f}, tile_size, false};
/* Iterate over rows and columns, moving the area by one tile size each iteration and copying that area into a new surface in the vector
* of tiles. */
std::vector<std::shared_ptr<SDL_Surface>> tiles;
for (int y = 0; y < count.y; y++)
{
area.left(0.0f);
for (int x = 0; x < count.x; x++)
{
tiles.push_back(sb::extract_area(source, area));
area.move({tile_size.x, 0.0f});
}
area.move({0.0f, tile_size.y});
}
return tiles;
}
int SDL_SetRenderDrawColor(SDL_Renderer* renderer, const Color& color)
{
return SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);

View File

@ -28,7 +28,7 @@
#include <fstream>
#include <cstdlib>
/* SDL */
/* SDL shared libraries */
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_pixels.h"
@ -42,6 +42,7 @@
#include "glm/gtx/integer.hpp"
#include "json/json.hpp"
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
#include "sdl2-gfx/SDL2_rotozoom.h"
/* SPACEBOX */
#include "Box.hpp"
@ -85,7 +86,6 @@ namespace sb
std::vector<fs::path> glob(fs::path);
fs::path get_next_file_name(fs::path, int = 0, std::string = "", std::string = "");
/*!
* Read the file at path into a string and return the string.
*
@ -118,6 +118,38 @@ namespace sb
*/
fs::path copy_file(fs::path to, fs::path from, bool overwrite_ok = false);
/*!
* Copy the pixel data in a given rectangular area of an SDL surface into a new SDL surface.
*
* @warning The coordinate system of the rectangular area is expected to be in SDL format, meaning y=0 at the top of the surface, and y=height
* at the bottom. However, the Box class uses GL format by default. To set the Y-axis to SDL format, pass false to Box::invert_y(bool).
*
* @param source surface to copy pixels from
* @param area rectangular area to copy
* @return shared pointer to a new SDL surface
*/
std::shared_ptr<SDL_Surface> extract_area(const std::shared_ptr<SDL_Surface>& surface, const sb::Box& area);
/*!
* Copy pixel data from a given SDL surface into a vector of new SDL surfaces, transforming the original surface into equally sized tiles.
* The number of tiles and size of each tile is determined by the count parameter. Count is a 2D vector indicating how many tiles per row
* and how many rows. For example, if count is {4, 3}, 12 tiles total will be created, 4 per row.
*
* The tiles will be in order from the top left tile, going row by row until the bottom right tile. Note that y=0 at the top of image, not
* the bottom, which is common in image manipulation, although is not the coordinate system GL uses, so tile 0 on the Y-axis is the top of
* the image in this function.
*
* If the count of tiles does not divide evenly into the number of pixels in the surface in either dimension, the rightmost and/or
* bottommost tiles will be smaller than the rest.
*
* The pixel data in the passed surface is copied into new shared pointers to new SDL surfaces.
*
* @param source surface to split into tiles
* @param count number of tiles as columns x rows
* @return vector of shared pointers to new SDL surfaces
*/
std::vector<std::shared_ptr<SDL_Surface>> extract_tiles_by_count(const std::shared_ptr<SDL_Surface>& source, const glm::ivec2& count);
/*!
* Return an unsorted vector of keys from the passed map.
*/