From 8346f43f212938610bb9ddd109db95cfdffe13d4 Mon Sep 17 00:00:00 2001 From: Frank DeMarco Date: Sat, 22 Aug 2020 20:06:06 -0400 Subject: [PATCH] - Pixel class generalizes pixel access, reads, writes, and applies pixel value modifications to textures of any SDL_TEXTUREACCESS_* and any pixel format - Color inherits SDL_Color directly instead of emulating it - added cast operator for converting Box to SDL_Rect --- src/Box.cpp | 5 ++ src/Box.hpp | 2 + src/Color.cpp | 41 +++++++------- src/Color.hpp | 27 +++++++--- src/Pixels.cpp | 135 ++++++++++++++++++++++++++++++++++++++++++++++ src/Pixels.hpp | 34 ++++++++++++ src/Sprite.cpp | 3 ++ src/Sprite.hpp | 1 + src/extension.cpp | 88 ++++++++++++++++-------------- src/extension.hpp | 15 +++--- 10 files changed, 277 insertions(+), 74 deletions(-) create mode 100644 src/Pixels.cpp create mode 100644 src/Pixels.hpp diff --git a/src/Box.cpp b/src/Box.cpp index 320667a..8cb9ba4 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -248,6 +248,11 @@ SDL_Rect Box::get_int_rect() const return {static_cast(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(rect.h)}; } +Box::operator SDL_Rect() const +{ + return {static_cast(rect.x), static_cast(rect.y), static_cast(rect.w), static_cast(rect.h)}; +} + void Box::clear() { set_nw(glm::vec2(0, 0)); diff --git a/src/Box.hpp b/src/Box.hpp index a29bb03..2a71a59 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -14,6 +14,7 @@ struct Segment; struct Box { + SDL_FRect rect = {0, 0, 0, 0}; Box(const glm::vec2& = {0, 0}, const glm::vec2& = {0, 0}); @@ -61,6 +62,7 @@ struct Box void set_center(const glm::vec2&); SDL_FRect* get_rect(); SDL_Rect get_int_rect() const; + operator SDL_Rect() const; void clear(); void scale(float, bool = false); void move(const glm::vec2&); diff --git a/src/Color.cpp b/src/Color.cpp index 59aa541..e206133 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -2,37 +2,36 @@ Color::Color() : Color(0, 0, 0, 255) {} -Color::Color(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) : r(r), g(g), b(b), a(a) {}; - Color::Color(const SDL_Color& color) : Color(color.r, color.g, color.b, color.a) {}; -void Color::set_rgb_float(const float& rf, const float& gf, const float& bf) +void Color::set_percent(const float& red, const float& green, const float& blue) { - r = std::round(255.0f * rf); - g = std::round(255.0f * gf); - b = std::round(255.0f * bf); + r = std::round(255.0f * red); + g = std::round(255.0f * green); + b = std::round(255.0f * blue); } -void Color::set_hsv(const float& h, const float& s, const float& v) +void Color::set_percent(const float& red, const float& green, const float& blue, const float& alpha) { - float rf, gf, bf; - HSVtoRGB(rf, gf, bf, h, s, v); - set_rgb_float(rf, gf, bf); + a = std::round(255.0f * alpha); + set_percent(red, green, blue); +} + +void Color::set_hsv(const float& hue, const float& saturation, const float& value) +{ + float red_percent, green_percent, blue_percent; + HSVtoRGB(red_percent, green_percent, blue_percent, hue, saturation, value); + set_percent(red_percent, green_percent, blue_percent); } void Color::shift_hue(float offset) { - float h, s, v; - float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f; - RGBtoHSV(rf, gf, bf, h, s, v); - h = std::fmod(h + offset, 360.0); - HSVtoRGB(rf, gf, bf, h, s, v); - set_rgb_float(rf, gf, bf); -} - -SDL_Color Color::get_sdl_color() -{ - return {r, g, b, a}; + float hue, saturation, value; + float red_percent = r / 255.0f, green_percent = g / 255.0f, blue_percent = b / 255.0f; + RGBtoHSV(red_percent, green_percent, blue_percent, hue, saturation, value); + hue = std::fmod(hue + offset, 360.0); + HSVtoRGB(red_percent, green_percent, blue_percent, hue, saturation, value); + set_percent(red_percent, green_percent, blue_percent); } std::ostream& operator<<(std::ostream& out, const Color& color) diff --git a/src/Color.hpp b/src/Color.hpp index 161ced6..dcce4ef 100644 --- a/src/Color.hpp +++ b/src/Color.hpp @@ -5,21 +5,36 @@ #include #include #include +#include #include "SDL_pixels.h" +#include "extension.hpp" -struct Color +struct Color : SDL_Color { - std::uint8_t r, g, b, a; - Color(); - Color(std::uint8_t, std::uint8_t, std::uint8_t, std::uint8_t = 255); Color(const SDL_Color&); - void set_rgb_float(const float&, const float&, const float&); + void set_percent(const float&, const float&, const float&); + void set_percent(const float&, const float&, const float&, const float&); void set_hsv(const float&, const float& = 1.0f, const float& = 1.0f); void shift_hue(float); - SDL_Color get_sdl_color(); + + template + Color(T red, T green, T blue, T alpha = 255) + { + if (std::is_floating_point()) + { + red = std::round(red); + green = std::round(green); + blue = std::round(blue); + alpha = std::round(alpha); + } + r = static_cast(red) & 255; + g = static_cast(green) & 255; + b = static_cast(blue) & 255; + a = static_cast(alpha) & 255; + } }; diff --git a/src/Pixels.cpp b/src/Pixels.cpp new file mode 100644 index 0000000..04d000a --- /dev/null +++ b/src/Pixels.cpp @@ -0,0 +1,135 @@ +#include "Pixels.hpp" + +Pixels::Pixels(SDL_Renderer* renderer, SDL_Texture* texture) : Pixels(renderer, texture, sfw::get_texture_box(texture)) {} + +Pixels::Pixels(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect& rect) : texture(texture), rect(rect) +{ + Uint32 format_enum; + int w, h; + SDL_QueryTexture(texture, &format_enum, &texture_access, &w, &h); + format = SDL_AllocFormat(format_enum); + if (texture_access == SDL_TEXTUREACCESS_STATIC || texture_access == SDL_TEXTUREACCESS_TARGET) + { + bool is_duplicate; + SDL_Texture* base; + if (texture_access == SDL_TEXTUREACCESS_STATIC) + { + base = sfw::duplicate_texture(renderer, texture); + is_duplicate = true; + } + else + { + base = texture; + is_duplicate = false; + } + SDL_SetRenderTarget(renderer, base); + int bytes_total = get_bytes_per_row() * rect.h; + source = operator new(bytes_total); + if (SDL_RenderReadPixels(renderer, &rect, format->format, source, format->BytesPerPixel * rect.w) < 0) + { + sfw::print_sdl_error("could not read pixels"); + operator delete(source); + } + else + { + allocated = true; + } + if (is_duplicate) + { + SDL_DestroyTexture(base); + } + } + else if (texture_access == SDL_TEXTUREACCESS_STREAMING) + { + int pitch; + if (SDL_LockTexture(texture, &rect, &source, &pitch) < 0) + { + sfw::print_sdl_error("could not lock texture"); + } + } + else + { + sfw::print_error("unknown texture format for loading pixels"); + } +} + +int Pixels::get_bytes_per_row() const +{ + return format->BytesPerPixel * rect.w; +} + +void* Pixels::operator()(int x, int y) +{ + std::uint8_t* access = static_cast(source); + if (x < 0 || x >= rect.w) + { + x = sfw::mod(x, static_cast(rect.w)); + } + if (y < 0 || y >= rect.y) + { + y = sfw::mod(y, static_cast(rect.h)); + } + return access + y * get_bytes_per_row() + x * format->BytesPerPixel; +} + +Color Pixels::operator()(int x, int y) const +{ + std::uint8_t* access = static_cast(source); + if (x < 0 || x >= rect.w) + { + x = sfw::mod(x, static_cast(rect.w)); + } + if (y < 0 || x >= rect.y) + { + y = sfw::mod(y, static_cast(rect.h)); + } + Uint32* pixel = reinterpret_cast(access + y * get_bytes_per_row() + x * format->BytesPerPixel); + Color color; + SDL_GetRGBA(*pixel, const_cast(format), &color.r, &color.g, &color.b, &color.a); + return color; +} + +void Pixels::set(const SDL_Color& color, int x, int y) +{ + std::uint32_t pixel = SDL_MapRGBA(const_cast(format), color.r, color.g, color.b, color.a); + if (format->BytesPerPixel == 1) + { + std::uint8_t* access = reinterpret_cast((*this)(x, y)); + *access = static_cast(pixel); + } + else if (format->BytesPerPixel == 2) + { + std::uint16_t* access = reinterpret_cast((*this)(x, y)); + *access = static_cast(pixel); + } + else + { + std::uint32_t* access = reinterpret_cast((*this)(x, y)); + *access = static_cast(pixel); + } +} + +void Pixels::apply() +{ + if (texture_access == SDL_TEXTUREACCESS_STATIC || texture_access == SDL_TEXTUREACCESS_TARGET) + { + SDL_UpdateTexture(texture, &rect, source, get_bytes_per_row()); + } + else + { + SDL_UnlockTexture(texture); + } +} + +Pixels::~Pixels() +{ + if (allocated) + { + operator delete(source); + } + if (texture_access == SDL_TEXTUREACCESS_STREAMING) + { + SDL_UnlockTexture(texture); + } + SDL_FreeFormat(format); +} diff --git a/src/Pixels.hpp b/src/Pixels.hpp new file mode 100644 index 0000000..d87352d --- /dev/null +++ b/src/Pixels.hpp @@ -0,0 +1,34 @@ +#ifndef Pixels_h_ +#define Pixels_h_ + +#include "SDL.h" +#include "Box.hpp" +#include "Color.hpp" +#include "extension.hpp" + +struct Sprite; + +struct Pixels +{ + + void* source = nullptr; + SDL_PixelFormat* format = nullptr; + SDL_Texture* texture; + int texture_access = 0; + SDL_Rect rect; + bool allocated = false; + + Pixels(SDL_Renderer*, SDL_Texture* texture); + Pixels(SDL_Renderer*, SDL_Texture* texture, const SDL_Rect&); + Pixels(Sprite&); + Pixels(Sprite&, const SDL_Rect&); + int get_bytes_per_row() const; + void* operator()(int x, int y); + Color operator()(int x, int y) const; + void set(const SDL_Color&, int x, int y); + void apply(); + ~Pixels(); + +}; + +#endif diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 2a680ea..6780fa9 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -797,3 +797,6 @@ void Frameset::reverse() { reversed = !reversed; } + +Pixels::Pixels(Sprite& sprite) : Pixels(sprite.get_renderer(), sprite.get_current_frame()) {} +Pixels::Pixels(Sprite& sprite, const SDL_Rect& rect) : Pixels(sprite.get_renderer(), sprite.get_current_frame(), rect) {} diff --git a/src/Sprite.hpp b/src/Sprite.hpp index df610a3..daae24e 100644 --- a/src/Sprite.hpp +++ b/src/Sprite.hpp @@ -156,6 +156,7 @@ struct Frameset }; +#include "Pixels.hpp" #include "Game.hpp" #endif diff --git a/src/extension.cpp b/src/extension.cpp index 4641dca..c286a25 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -201,48 +201,56 @@ SDL_Texture* sfw::get_hue_shifted_texture(SDL_Renderer* renderer, SDL_Texture* b SDL_Texture* hue_shifted_texture = sfw::duplicate_texture(renderer, base); Uint32 pixel_format; int w, h; - SDL_QueryTexture(hue_shifted_texture, &pixel_format, NULL, &w, &h); - 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) + if (SDL_QueryTexture(hue_shifted_texture, &pixel_format, nullptr, &w, &h) < 0) { - print_sdl_error("Could not read pixels"); + print_sdl_error("could not query texture"); } else { - Color rgba; - for (int ii = 0; ii < length; ii++) + 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) { - SDL_GetRGBA(pixels[ii], const_cast(pixel_format_struct), - &rgba.r, &rgba.g, &rgba.b, &rgba.a); - rgba.shift_hue(offset); - pixels[ii] = SDL_MapRGBA(const_cast(pixel_format_struct), rgba.r, rgba.g, rgba.b, rgba.a); + print_sdl_error("Could not read pixels"); } - if (SDL_UpdateTexture(hue_shifted_texture, NULL, pixels, bytes_per_row) < 0) + else { - print_sdl_error("Could not apply hue shifted pixels update to texture"); + Color rgba; + for (int ii = 0; ii < length; ii++) + { + SDL_GetRGBA(pixels[ii], const_cast(pixel_format_struct), + &rgba.r, &rgba.g, &rgba.b, &rgba.a); + rgba.shift_hue(offset); + pixels[ii] = SDL_MapRGBA(const_cast(pixel_format_struct), rgba.r, rgba.g, rgba.b, rgba.a); + } + if (SDL_UpdateTexture(hue_shifted_texture, NULL, pixels, bytes_per_row) < 0) + { + print_sdl_error("Could not apply hue shifted pixels update to texture"); + } } + delete[] pixels; + SDL_FreeFormat(pixel_format_struct); } - delete[] pixels; - SDL_FreeFormat(pixel_format_struct); return hue_shifted_texture; } -SDL_Texture* sfw::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base, Uint32 format) +SDL_Texture* sfw::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base) { Box box = get_texture_box(base); - return duplicate_texture(renderer, base, box.get_size(), format); + return duplicate_texture(renderer, base, box.get_size()); } -SDL_Texture* sfw::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base, const glm::vec2& size, Uint32 format) +SDL_Texture* sfw::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) { @@ -256,10 +264,10 @@ SDL_Texture* sfw::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base, c } SDL_SetTextureBlendMode(base, SDL_BLENDMODE_NONE); SDL_SetTextureBlendMode(duplicate, SDL_BLENDMODE_BLEND); - if ((SDL_RenderCopyF(renderer, base, NULL, NULL)) < 0) + if ((SDL_RenderCopyF(renderer, base, nullptr, nullptr)) < 0) { print_sdl_error("could not render base onto duplicate"); - return NULL; + return nullptr; } SDL_SetTextureBlendMode(base, original_blend_mode); SDL_SetTextureBlendMode(duplicate, original_blend_mode); @@ -267,27 +275,29 @@ SDL_Texture* sfw::duplicate_texture(SDL_Renderer* renderer, SDL_Texture* base, c } SDL_Texture* sfw::get_remapped_texture( - SDL_Renderer* renderer, SDL_Texture* base, const std::map& map, Uint32 format) + SDL_Renderer* renderer, SDL_Texture* base, const std::map& map) { - SDL_Texture* remapped = duplicate_texture(renderer, base, format); - if (remapped == NULL) + SDL_Texture* remapped = duplicate_texture(renderer, base); + if (remapped == nullptr) { print_sdl_error("could not duplicate base texture"); - return NULL; + return nullptr; } if ((SDL_SetRenderTarget(renderer, remapped)) < 0) { print_sdl_error("could not set render target to remapped texture"); - return NULL; + return nullptr; } Box box = get_texture_box(remapped); + std::uint32_t format; + SDL_QueryTexture(remapped, &format, nullptr, nullptr, nullptr); int bytes_per_pixel = SDL_BYTESPERPIXEL(format); int bytes_total = bytes_per_pixel * box.get_w() * box.get_h(); unsigned char* pixels = new unsigned char[bytes_total]; - if ((SDL_RenderReadPixels(renderer, NULL, format, pixels, bytes_total / box.get_h())) < 0) + if ((SDL_RenderReadPixels(renderer, nullptr, format, pixels, bytes_total / box.get_h())) < 0) { print_sdl_error("could not read pixels after setting remapped texture as target"); - return NULL; + return nullptr; } SDL_Color color; for (int ii = 0; ii < bytes_total; ii += bytes_per_pixel) @@ -312,7 +322,7 @@ SDL_Texture* sfw::get_remapped_texture( } } } - if (SDL_UpdateTexture(remapped, NULL, pixels, bytes_total / box.get_h()) < 0) + if (SDL_UpdateTexture(remapped, nullptr, pixels, bytes_total / box.get_h()) < 0) { print_sdl_error("could not update remapped texture"); } @@ -321,19 +331,19 @@ SDL_Texture* sfw::get_remapped_texture( } SDL_Texture* sfw::get_remapped_texture( - SDL_Renderer* renderer, const std::string& path, const std::map& map, Uint32 format) + SDL_Renderer* renderer, const std::string& path, const std::map& map) { SDL_Texture* base = IMG_LoadTexture(renderer, path.c_str()); - if (base == NULL) + if (base == nullptr) { print_sdl_error("error loading file"); - return NULL; + return nullptr; } - SDL_Texture* remapped = get_remapped_texture(renderer, base, map, format); - if (remapped == NULL) + SDL_Texture* remapped = get_remapped_texture(renderer, base, map); + if (remapped == nullptr) { print_error("could not remap texture"); - return NULL; + return nullptr; } SDL_DestroyTexture(base); return remapped; diff --git a/src/extension.hpp b/src/extension.hpp index 4195dc7..574ea77 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -39,6 +39,8 @@ namespace sfw Box get_texture_box(SDL_Texture*); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&, const Box&); + void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector>&); + void apply_array_to_texture(SDL_Renderer*, SDL_Texture*, std::vector>&, const Box&); std::vector get_halo_frames( Node&, float, int, const std::vector& = {{0, 0, 0, 255}, {255, 255, 255, 255}}, float = 4.0f, bool = true); std::vector get_portal_frames(SDL_Renderer*, glm::vec2, float = 60, float = 30, int = 4, int = 6); @@ -47,13 +49,10 @@ namespace sfw SDL_Texture* get_filled_texture(SDL_Renderer*, glm::vec2, const SDL_Color&, Uint32 = SDL_PIXELFORMAT_RGBA32); SDL_Texture* get_filled_texture(SDL_Renderer*, glm::vec2, SDL_Texture*, Uint32 = SDL_PIXELFORMAT_RGBA32); SDL_Texture* get_hue_shifted_texture(SDL_Renderer*, SDL_Texture*, float); - SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*, Uint32 = SDL_PIXELFORMAT_RGBA32); - SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*, const glm::vec2&, Uint32 = SDL_PIXELFORMAT_RGBA32); - SDL_Texture* get_remapped_texture( - SDL_Renderer*, SDL_Texture*, const std::map&, Uint32 = SDL_PIXELFORMAT_RGBA32); - SDL_Texture* get_remapped_texture( - SDL_Renderer*, const std::string&, const std::map&, - Uint32 = SDL_PIXELFORMAT_RGBA32); + SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*); + SDL_Texture* duplicate_texture(SDL_Renderer*, SDL_Texture*, const glm::vec2&); + SDL_Texture* get_remapped_texture(SDL_Renderer*, SDL_Texture*, const std::map&); + SDL_Texture* get_remapped_texture(SDL_Renderer*, const std::string&, const std::map&); SDL_Texture* get_pixel_scaled_texture(SDL_Renderer*, SDL_Texture*, int = 1, int = scaler::scale2x); std::vector glob(fs::path); fs::path get_next_file_name( @@ -80,7 +79,7 @@ namespace sfw } template - float mod(N a, N b) + inline float mod(N a, N b) { return (b + (a % b)) % b; }