spacebox/src/extension.cpp

703 lines
23 KiB
C++

/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#include "Pixels.hpp"
#include "extension.hpp"
/* Edit a vector in place, giving it the specified magnitude while maintaining the direction */
void sb::set_magnitude(glm::vec2& vector, float magnitude)
{
vector = glm::normalize(vector) * magnitude;
}
/* Return coordinates of a point x, y at specified angle on a circle described by center and radius */
glm::vec2 sb::point_on_circle(const glm::vec2& center, float radius, float angle)
{
return {center.x + std::sin(angle) * radius, center.y - std::cos(angle) * radius};
}
/* Return a point x, y at specified angle on a circle at the origin with specified radius (default 1) */
glm::vec2 sb::point_on_circle(float angle, float radius)
{
return point_on_circle({0, 0}, radius, angle);
}
/* Fill a pre-initialized vector with points evenly spaced around a circle starting at the angle offset (defaults to 0) */
void sb::points_on_circle(std::vector<glm::vec2>& points, int count, float radius, const glm::vec2& center, float offset)
{
float step = glm::two_pi<float>() / count;
for (int ii = 0; ii < count; ii++)
{
points.push_back(point_on_circle(center, radius, ii * step + offset));
}
}
/* Return a vector of count number of points evenly spaced around a circle starting at the angle offset (defaults to 0) */
std::vector<glm::vec2> sb::points_on_circle(int count, float radius, const glm::vec2& center, float offset)
{
std::vector<glm::vec2> points;
points.reserve(count);
points_on_circle(points, count, radius, center, offset);
return points;
}
Box sb::get_texture_box(SDL_Texture* texture)
{
int width, height;
SDL_QueryTexture(texture, nullptr, nullptr, &width, &height);
return Box(glm::vec2(0, 0), glm::vec2(width, height));
}
glm::vec2 sb::fit_and_preserve_aspect(const glm::vec2& inner, const glm::vec2& outer)
{
glm::vec2 delta = inner - outer;
float aspect = inner.x / inner.y;
glm::vec2 fit;
if (delta.x > delta.y)
{
fit.x = outer.x;
fit.y = fit.x / aspect;
}
else
{
fit.y = outer.y;
fit.x = fit.y * aspect;
}
return fit;
}
std::vector<std::vector<Box>> sb::get_blinds_boxes(glm::vec2 size, float step, int count)
{
std::vector<Box> blinds;
float blind_height = size.y / count;
for (int ii = 1; ii <= count; ii++)
{
blinds.push_back(Box({0, blind_height * ii}, {size.x, 0}));
}
float inflate_per_frame = blind_height * step;
std::vector<std::vector<Box>> frames;
float bottom_save;
while (blinds[0].height() < blind_height)
{
frames.push_back({});
for (Box& blind : blinds)
{
bottom_save = blind.bottom();
blind.expand({0, inflate_per_frame});
blind.bottom(bottom_save);
frames.back().push_back(blind);
}
}
return frames;
}
void sb::populate_pixel_2d_array(SDL_Renderer* renderer, SDL_Texture* texture, std::vector<std::vector<SDL_Color>>& pixels)
{
populate_pixel_2d_array(renderer, texture, pixels, get_texture_box(texture));
}
void sb::populate_pixel_2d_array(
SDL_Renderer* renderer, SDL_Texture* texture, std::vector<std::vector<SDL_Color>>& pixels, const Box& region)
{
int access;
if (SDL_QueryTexture(texture, nullptr, &access, nullptr, nullptr) < 0)
{
sb::Log::sdl_error("Could not query texture for access flag");
}
else
{
if (access != SDL_TEXTUREACCESS_TARGET)
{
texture = duplicate_texture(renderer, texture);
}
if (SDL_SetRenderTarget(renderer, texture) < 0)
{
sb::Log::sdl_error("Could not set render target");
}
else
{
Uint32 format = SDL_PIXELFORMAT_RGBA32;
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
int bytes_per_row = bytes_per_pixel * region.width();
int bytes_total = bytes_per_row * region.height();
Uint8* source = new Uint8[bytes_total];
SDL_Rect int_rect = region;
if (SDL_RenderReadPixels(renderer, &int_rect, format, source, bytes_per_row) < 0)
{
sb::Log::sdl_error("Could not read pixels after setting remapped texture as target");
}
else
{
pixels.reserve(region.width());
for (int x = 0; x < region.width(); x++)
{
std::vector<SDL_Color> column;
pixels.push_back(column);
pixels[x].reserve(region.height());
}
for (int y = 0, ii = 0; y < region.height(); y++)
{
for (int x = 0; x < region.width(); x++)
{
pixels[x][y] = {source[ii++], source[ii++], source[ii++], source[ii++]};
}
}
}
delete[] source;
}
}
}
std::vector<SDL_Texture*> sb::get_halo_frames(
SDL_Renderer* renderer, float radius, int segment_count, const std::vector<Color>& colors, float min_radius, bool fade)
{
std::vector<SDL_Texture*> frames;
frames.reserve(segment_count);
SDL_Texture* frame;
float alpha = 255, alpha_step = 255.0f / segment_count, segment_radius;
int color_count = colors.size();
Color color;
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
for (int color_offset = 0; color_offset < color_count; color_offset++)
{
if (fade)
{
alpha = alpha_step;
}
frame = get_filled_texture(renderer, {2 * radius, 2 * radius}, {0, 0, 0, 0});
SDL_SetTextureBlendMode(frame, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(renderer, frame);
for (int segment_ii = 0; segment_ii < segment_count; segment_ii++)
{
color = colors[(color_offset + segment_ii) % color_count];
color.a = std::round(alpha);
segment_radius = min_radius + (segment_count - 1.0f - segment_ii) / (segment_count - 1.0f) * (radius - min_radius);
filledCircleRGBA(renderer, radius, radius, static_cast<int>(std::round(segment_radius)),
color.r, color.g, color.b, color.a);
if (fade)
{
alpha += alpha_step;
}
}
frames.push_back(frame);
}
return frames;
}
std::vector<SDL_Texture*> sb::get_portal_frames(
SDL_Renderer* renderer, glm::vec2 size, float hue_start, float hue_end, int dy, int count)
{
std::vector<SDL_Texture*> frames;
frames.reserve(count);
float y_margin = 10;
float max_y = size.y - y_margin;
std::vector<float> hues = range_count(hue_start, hue_end, count);
SDL_Texture* frame;
Color color;
for (int frame_ii = 0; frame_ii < count; frame_ii++)
{
frame = get_filled_texture(renderer, size, {255, 255, 255, 0});
SDL_SetRenderTarget(renderer, frame);
SDL_SetTextureBlendMode(frame, SDL_BLENDMODE_BLEND);
for (int ellipse_ii = 0, y = max_y; y > y_margin - 3; ellipse_ii++, y -= dy)
{
color.a = y / max_y * 255.0f;
color.set_hsv(hues[mod(ellipse_ii - frame_ii, count)]);
aaFilledEllipseRGBA(renderer, size.x / 2, y, size.x / 2, y_margin - 3, color.r, color.g, color.b, color.a);
}
frames.push_back(frame);
}
return frames;
}
void sb::fill_texture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Color& color, const Box& box)
{
SDL_SetRenderTarget(renderer, texture);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_RenderFillRectF(renderer, &box);
}
void sb::fill_texture(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Color& color)
{
fill_texture(renderer, texture, color, get_texture_box(texture));
}
void sb::fill_texture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Texture* tile, const Box& box)
{
Box texture_box = get_texture_box(texture), tile_box = get_texture_box(tile);
SDL_FRect draw_rect;
if (SDL_SetRenderTarget(renderer, texture) < 0)
{
sb::Log::sdl_error("could not set render target");
}
else
{
SDL_Rect int_rect = box;
if (SDL_RenderSetClipRect(renderer, &int_rect) < 0)
{
sb::Log::sdl_error("could not set clip");
}
else
{
for (int x = 0; x < texture_box.width(); x += tile_box.width())
{
for (int y = 0; y < texture_box.height(); y += tile_box.height())
{
draw_rect = {(float) x, (float) y, tile_box.width(), tile_box.height()};
SDL_RenderCopyF(renderer, tile, nullptr, &draw_rect);
}
}
SDL_RenderSetClipRect(renderer, nullptr);
}
}
}
void sb::fill_texture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Texture* tile)
{
fill_texture(renderer, texture, tile, get_texture_box(texture));
}
SDL_Texture* sb::get_filled_texture(SDL_Renderer* renderer, glm::vec2 size, const SDL_Color& color, Uint32 format)
{
SDL_Texture* texture;
if ((texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET, size.x, size.y)) == nullptr)
{
sb::Log::sdl_error("could not create texture to fill");
}
else
{
fill_texture(renderer, texture, color);
}
return texture;
}
SDL_Texture* sb::get_filled_texture(SDL_Renderer* renderer, glm::vec2 size, SDL_Texture* tile, Uint32 format)
{
SDL_Texture* texture;
if ((texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET, size.x, size.y)) == nullptr)
{
sb::Log::sdl_error("could not create texture to fill");
}
else
{
fill_texture(renderer, texture, tile);
}
return texture;
}
SDL_Texture* sb::get_hue_shifted_texture(SDL_Renderer* renderer, SDL_Texture* base, float offset)
{
SDL_Texture* hue_shifted_texture = duplicate_texture(renderer, base);
Uint32 pixel_format;
int w, h;
if (SDL_QueryTexture(hue_shifted_texture, &pixel_format, nullptr, &w, &h) < 0)
{
sb::Log::sdl_error("could not query texture");
}
else
{
SDL_PixelFormat* pixel_format_struct = SDL_AllocFormat(pixel_format);
SDL_SetRenderTarget(renderer, hue_shifted_texture);
int bytes_per_pixel = SDL_BYTESPERPIXEL(pixel_format);
int bytes_per_row = bytes_per_pixel * w;
int bytes_total = bytes_per_row * h;
int length = bytes_total / 4 + (bytes_total % 4 ? 1 : 0);
Uint32* pixels = new Uint32[length];
if (SDL_RenderReadPixels(renderer, NULL, pixel_format, pixels, bytes_per_row) < 0)
{
sb::Log::sdl_error("Could not read pixels");
}
else
{
Color rgba;
for (int ii = 0; ii < length; ii++)
{
SDL_GetRGBA(pixels[ii], const_cast<const SDL_PixelFormat*>(pixel_format_struct),
&rgba.r, &rgba.g, &rgba.b, &rgba.a);
rgba.shift_hue(offset);
pixels[ii] = SDL_MapRGBA(const_cast<const SDL_PixelFormat*>(pixel_format_struct), rgba.r, rgba.g, rgba.b, rgba.a);
}
if (SDL_UpdateTexture(hue_shifted_texture, NULL, pixels, bytes_per_row) < 0)
{
sb::Log::sdl_error("Could not apply hue shifted pixels update to texture");
}
}
delete[] pixels;
SDL_FreeFormat(pixel_format_struct);
}
return hue_shifted_texture;
}
SDL_Texture* sb::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base)
{
Box box = get_texture_box(base);
return duplicate_texture(renderer, base, box.size());
}
SDL_Texture* sb::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base, const glm::vec2& size)
{
SDL_BlendMode original_blend_mode;
SDL_GetTextureBlendMode(base, &original_blend_mode);
Uint32 format;
SDL_QueryTexture(base, &format, nullptr, nullptr, nullptr);
SDL_Texture* duplicate = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET, size.x, size.y);
if (duplicate == NULL)
{
sb::Log::sdl_error("could not create texture from base");
return NULL;
}
if ((SDL_SetRenderTarget(renderer, duplicate)) < 0)
{
sb::Log::sdl_error("could not set render target to duplicate");
return NULL;
}
SDL_SetTextureBlendMode(base, SDL_BLENDMODE_NONE);
SDL_SetTextureBlendMode(duplicate, SDL_BLENDMODE_BLEND);
if ((SDL_RenderCopyF(renderer, base, nullptr, nullptr)) < 0)
{
sb::Log::sdl_error("could not render base onto duplicate");
return nullptr;
}
SDL_SetTextureBlendMode(base, original_blend_mode);
SDL_SetTextureBlendMode(duplicate, original_blend_mode);
return duplicate;
}
SDL_Texture* sb::get_remapped_texture(
SDL_Renderer* renderer, SDL_Texture* base, const std::map<Color, Color>& map)
{
SDL_Texture* remapped = duplicate_texture(renderer, base);
if (remapped == nullptr)
{
sb::Log::sdl_error("could not duplicate base texture");
return nullptr;
}
if ((SDL_SetRenderTarget(renderer, remapped)) < 0)
{
sb::Log::sdl_error("could not set render target to remapped texture");
return nullptr;
}
Pixels pixels = Pixels(renderer, remapped);
for (int x = 0; x < pixels.rect.w; x++)
{
for (int y = 0; y < pixels.rect.h; y++)
{
for (auto& [original, replacement] : map)
{
if (pixels.get(x, y) == original)
{
pixels.set(replacement, x, y);
}
}
}
}
pixels.apply();
return remapped;
}
SDL_Texture* sb::get_remapped_texture(
SDL_Renderer* renderer, const std::string& path, const std::map<Color, Color>& map)
{
SDL_Texture* base = IMG_LoadTexture(renderer, path.c_str());
if (base == nullptr)
{
sb::Log::sdl_error("error loading file");
return nullptr;
}
SDL_Texture* remapped = get_remapped_texture(renderer, base, map);
if (remapped == nullptr)
{
sb::Log::log("could not remap texture", sb::Log::ERROR);
return nullptr;
}
SDL_DestroyTexture(base);
return remapped;
}
#include "superxbr.cpp"
/*
- Base texture must be set to SDL_TEXTUREACCESS_TARGET
- Scale2x implementation based on http://www.scale2x.it/algorithm.html
*/
SDL_Texture* sb::get_pixel_scaled_texture(SDL_Renderer* renderer, SDL_Texture* base, int count, int version)
{
if ((SDL_SetRenderTarget(renderer, base)) < 0)
{
sb::Log::sdl_error("could not set render target to remapped texture");
return nullptr;
}
glm::ivec2 size = get_texture_box(base).size();
Uint32 format = SDL_PIXELFORMAT_RGBA32;
int bytes_per_pixel, bytes_per_row, bytes_total;
Uint32 *src, *dst, *src_begin, *dst_begin;
for (int ii = 0; ii < count; ii++, size *= 2)
{
bytes_per_pixel = SDL_BYTESPERPIXEL(format);
bytes_per_row = bytes_per_pixel * size.x;
bytes_total = bytes_per_row * size.y;
if (ii == 0)
{
src = new Uint32[size.x * size.y];
src_begin = src;
if ((SDL_RenderReadPixels(renderer, NULL, format, src, bytes_per_row)) < 0)
{
sb::Log::sdl_error("could not read pixels after setting remapped texture as target");
return NULL;
}
}
else
{
src = dst_begin;
src_begin = src;
}
dst = new Uint32[size.x * size.y * 4];
dst_begin = dst;
if (version == scaler::scale2x)
{
Uint32 A, B, C, D, E, F, G, H, I;
for (int y = 0; y < size.y; y++)
{
for (int x = 0; x < size.x; x++)
{
E = *src;
B = y == 0 ? E : *(src - size.x);
D = x == 0 ? E : *(src - 1);
F = x == size.x - 1 ? E : *(src + 1);
H = y == size.y - 1 ? E : *(src + size.x);
if (y != 0 && x != 0 && y != size.y - 1 && x != size.x - 1)
{
A = *(src - size.x - 1);
C = *(src - size.x + 1);
G = *(src + size.x - 1);
I = *(src + size.x + 1);
}
if (x == 0)
{
A = B;
G = H;
}
if (y == 0)
{
A = D;
C = F;
}
if (x == size.x - 1)
{
C = B;
I = H;
}
if (y == size.y - 1)
{
G = D;
I = F;
}
if (B != H && D != F)
{
*dst = D == B ? D : E;
*(dst + 1) = B == F ? F : E;
*(dst + 2 * size.x) = D == H ? D : E;
*(dst + 2 * size.x + 1) = H == F ? F : E;
}
else
{
*dst = E;
*(dst + 1) = E;
*(dst + 2 * size.x) = E;
*(dst + 2 * size.x + 1) = E;
}
src++;
dst += 2;
}
dst += 2 * size.x;
}
}
else if (version == scaler::xbr)
{
scaleSuperXBRT<2>(src, dst, size.x, size.y);
}
delete[] src_begin;
}
SDL_Texture* scaled = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_TARGET, size.x, size.y);
if (scaled == nullptr)
{
sb::Log::sdl_error("could not create scaled texture");
}
if (SDL_UpdateTexture(scaled, nullptr, dst_begin, bytes_per_row * 2) < 0)
{
sb::Log::sdl_error("could not copy pixels to scaled texture");
}
delete[] dst_begin;
return scaled;
}
std::vector<fs::path> sb::glob(fs::path query)
{
fs::path basename = query.parent_path();
if (basename == "")
{
basename = ".";
}
std::regex expression(query.string());
std::vector<fs::path> files;
for (auto& entry: fs::directory_iterator(basename))
{
if (std::regex_match(entry.path().string(), expression))
{
files.push_back(entry.path());
}
}
std::sort(files.begin(), files.end());
return files;
}
SDL_Surface* sb::get_surface_from_pixels(Pixels& pixels)
{
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
pixels.source, pixels.rect.w, pixels.rect.h, pixels.format->BitsPerPixel,
pixels.get_bytes_per_row(), pixels.format->Rmask, pixels.format->Gmask, pixels.format->Bmask,
pixels.format->Amask);
if (surface == nullptr)
{
sb::Log::sdl_error("could not create RGB surface from texture pixel data");
}
else
{
return surface;
}
return nullptr;
}
fs::path sb::get_next_file_name(fs::path directory, int zfill, std::string prefix, std::string extension)
{
std::stringstream file_pattern;
file_pattern << prefix << "([0-9]+)" << extension;
fs::path query = directory / file_pattern.str();
std::vector<fs::path> files = glob(query);
int index = 1;
if (files.size())
{
const std::string last = files.back().string();
std::smatch matches;
std::regex_match(last, matches, std::regex(query.string()));
index = std::stoi(matches[1]) + 1;
}
std::stringstream filename;
fs::path path;
do
{
filename << prefix << pad(index++, zfill) << extension;
path = directory / filename.str();
filename.str("");
filename.clear();
}
while (fs::exists(path));
return path;
}
std::string sb::file_to_string(const fs::path& path)
{
std::string contents = "";
#if !defined(__ANDROID__) && !defined(ANDROID)
std::ostringstream log_message;
if (!fs::exists(path))
{
log_message << "No file found at " << path;
sb::Log::log(log_message, sb::Log::Level::WARN);
}
else
{
std::ifstream file;
file.open(path);
if (!file.is_open())
{
log_message << "Failed to open " << path;
sb::Log::log(log_message, sb::Log::Level::WARN);
}
else
{
/* Read file using std::string's range constructor, from the beginning of the file stream to end of stream (represented
* by {} (?)) */
contents = std::string(std::istreambuf_iterator(file), {});
std::size_t size = file.tellg();
log_message << "Opened file " << path << " (" << (size / 1000.0f) << "KB)";
sb::Log::log(log_message);
sb::Log::log(contents, sb::Log::Level::DEBUG);
}
}
#else
std::unique_ptr<SDL_RWops, decltype(&SDL_RWclose)> sdl_rw(SDL_RWFromFile(path.c_str(), "r"), SDL_RWclose);
if (sdl_rw.get() == nullptr)
{
__android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "Unable to open file %s", SDL_GetError());
}
else
{
int byte_count = SDL_RWsize(sdl_rw.get());
__android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "File at %s is %fKB", path.c_str(), (byte_count / 1000.0f));
contents.resize(byte_count);
int nb_read_total = 0, nb_read = 1;
while (nb_read_total < byte_count && nb_read != 0)
{
nb_read = SDL_RWread(sdl_rw.get(), &contents[nb_read_total], 1, (byte_count - nb_read_total));
nb_read_total += nb_read;
}
if (nb_read_total != byte_count)
{
__android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "File could not be read because of a mismatch in file size and number of bytes read");
}
else
{
__android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "%s", contents.c_str());
}
}
#endif
return contents;
}
int SDL_SetRenderDrawColor(SDL_Renderer* renderer, const Color& color)
{
return SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
}
int SDL_RenderFillRect(SDL_Renderer* renderer, const Box& box)
{
SDL_Rect rect = box;
return SDL_RenderFillRect(renderer, &rect);
}
int lineColor(SDL_Renderer* renderer, const Segment& segment, const Color& color, std::uint8_t thickness)
{
if (thickness == 1)
{
return lineColor(renderer, segment.start().x, segment.start().y, segment.end().x, segment.end().y, color);
}
else
{
return thickLineColor(renderer, segment.start().x, segment.start().y, segment.end().x, segment.end().y, thickness, color);
}
}
std::ostream& std::operator<<(std::ostream& out, const SDL_Color& color)
{
out << "{" << static_cast<int>(color.r) << ", " << static_cast<int>(color.g) << ", " <<
static_cast<int>(color.b) << ", " << static_cast<int>(color.a) << "}";
return out;
}