diff --git a/src/Box.cpp b/src/Box.cpp index 2b9933b..e10a083 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -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(left()), static_cast(top()), static_cast(width()), static_cast(height())}; diff --git a/src/Box.hpp b/src/Box.hpp index 17c381d..89fd9fd 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -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(); /*! diff --git a/src/extension.cpp b/src/extension.cpp index dc3fc2e..b801f7d 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -707,6 +707,76 @@ fs::path sb::copy_file(fs::path from, fs::path to, bool overwrite_ok) return destination; } +std::shared_ptr sb::extract_area(const std::shared_ptr& 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 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 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> sb::extract_tiles_by_count(const std::shared_ptr& 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> 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); diff --git a/src/extension.hpp b/src/extension.hpp index 1cae1e2..4dfaac1 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -28,7 +28,7 @@ #include #include -/* 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 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 extract_area(const std::shared_ptr& 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> extract_tiles_by_count(const std::shared_ptr& source, const glm::ivec2& count); + /*! * Return an unsorted vector of keys from the passed map. */