diff --git a/gfx/canvas_command_saver.h b/gfx/canvas_command_saver.h index 334f4686..db63fc95 100644 --- a/gfx/canvas_command_saver.h +++ b/gfx/canvas_command_saver.h @@ -80,6 +80,13 @@ struct DrawTextCmd { [[nodiscard]] bool operator==(DrawTextCmd const &) const = default; }; +struct DrawPixelsCmd { + geom::Rect rect{}; + std::vector rgba_data{}; + + [[nodiscard]] bool operator==(DrawPixelsCmd const &) const = default; +}; + using CanvasCommand = std::variant; + DrawTextCmd, + DrawPixelsCmd>; class CanvasCommandSaver : public ICanvas { public: @@ -124,6 +132,10 @@ class CanvasCommandSaver : public ICanvas { cmds_.emplace_back(DrawTextCmd{position, std::string{text}, std::string{font.font}, size.px, style, color}); } + void draw_pixels(geom::Rect const &rect, std::span rgba_data) override { + cmds_.emplace_back(DrawPixelsCmd{rect, {rgba_data.begin(), rgba_data.end()}}); + } + // [[nodiscard]] std::vector take_commands() { return std::exchange(cmds_, {}); } @@ -156,6 +168,8 @@ class CanvasCommandVisitor { canvas_.draw_text(cmd.position, cmd.text, {cmd.font}, {cmd.size}, cmd.style, cmd.color); } + void operator()(DrawPixelsCmd const &cmd) { canvas_.draw_pixels(cmd.rect, cmd.rgba_data); } + private: ICanvas &canvas_; }; diff --git a/gfx/canvas_command_saver_test.cpp b/gfx/canvas_command_saver_test.cpp index 3c2901c5..a27104aa 100644 --- a/gfx/canvas_command_saver_test.cpp +++ b/gfx/canvas_command_saver_test.cpp @@ -97,6 +97,12 @@ int main() { {1, 2}, "hello!"s, {{"comic sans"}}, 11, FontStyle::Normal, {1, 2, 3}}}); }); + etest::test("CanvasCommandSaver::draw_pixels", [] { + CanvasCommandSaver saver; + saver.draw_pixels({1, 2, 3, 4}, {{0x12, 0x34, 0x56, 0x78}}); + expect_eq(saver.take_commands(), CanvasCommands{DrawPixelsCmd{{1, 2, 3, 4}, {0x12, 0x34, 0x56, 0x78}}}); + }); + etest::test("replay_commands", [] { CanvasCommandSaver saver; saver.clear(gfx::Color{}); @@ -110,6 +116,7 @@ int main() { saver.draw_text({10, 10}, "beep beep boop!"sv, {"helvetica"}, {42}, FontStyle::Italic, {3, 2, 1}); saver.draw_text({1, 5}, "hello?"sv, {{{"font1"}, {"font2"}}}, {42}, FontStyle::Normal, {1, 2, 3}); saver.clear(gfx::Color{1, 2, 3}); + saver.draw_pixels({1, 2, 3, 4}, {{0x12, 0x34, 0x56, 0x78}}); auto cmds = saver.take_commands(); CanvasCommandSaver replayed; diff --git a/gfx/gfx_example.cpp b/gfx/gfx_example.cpp index 2ed00d71..76f44774 100644 --- a/gfx/gfx_example.cpp +++ b/gfx/gfx_example.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -78,6 +79,9 @@ int main(int argc, char **argv) { gfx::FontStyle::Italic | gfx::FontStyle::Bold | gfx::FontStyle::Underlined | gfx::FontStyle::Strikethrough, kHotPink); + auto px = std::to_array( + {100, 100, 100, 0xff, 200, 200, 200, 0xff, 50, 50, 50, 0xff, 200, 0, 0, 0xff}); + canvas->draw_pixels({1, 1, 2, 2}, px); window.display(); } diff --git a/gfx/icanvas.h b/gfx/icanvas.h index 7ceab8ba..fe767d2e 100644 --- a/gfx/icanvas.h +++ b/gfx/icanvas.h @@ -51,6 +51,7 @@ class ICanvas { virtual void draw_rect(geom::Rect const &, Color const &, Borders const &, Corners const &) = 0; virtual void draw_text(geom::Position, std::string_view, std::span, FontSize, FontStyle, Color) = 0; virtual void draw_text(geom::Position, std::string_view, Font, FontSize, FontStyle, Color) = 0; + virtual void draw_pixels(geom::Rect const &, std::span rgba_data) = 0; }; } // namespace gfx diff --git a/gfx/opengl_canvas.h b/gfx/opengl_canvas.h index bce1afaa..77684147 100644 --- a/gfx/opengl_canvas.h +++ b/gfx/opengl_canvas.h @@ -28,6 +28,7 @@ class OpenGLCanvas final : public ICanvas { void draw_rect(geom::Rect const &, Color const &, Borders const &, Corners const &) override; void draw_text(geom::Position, std::string_view, std::span, FontSize, FontStyle, Color) override {} void draw_text(geom::Position, std::string_view, Font, FontSize, FontStyle, Color) override {} + void draw_pixels(geom::Rect const &, std::span) override {} private: OpenGLShader border_shader_; diff --git a/gfx/sfml_canvas.cpp b/gfx/sfml_canvas.cpp index 89fe4767..0df939d1 100644 --- a/gfx/sfml_canvas.cpp +++ b/gfx/sfml_canvas.cpp @@ -10,11 +10,13 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -114,6 +116,7 @@ void SfmlCanvas::set_viewport_size(int width, int height) { void SfmlCanvas::clear(Color c) { target_.clear(sf::Color(c.as_rgba_u32())); + textures_.clear(); } void SfmlCanvas::fill_rect(geom::Rect const &rect, Color color) { @@ -238,4 +241,20 @@ void SfmlCanvas::draw_text( target_.draw(drawable); } +void SfmlCanvas::draw_pixels(geom::Rect const &rect, std::span rgba_data) { + assert(rgba_data.size() == static_cast(rect.width * rect.height * 4)); + sf::Image img; + // Textures need to be kept around while they're displayed. This will be + // cleared when the canvas is cleared. + sf::Texture &texture = textures_.emplace_back(); + texture.create(static_cast(rect.width), static_cast(rect.height)); + texture.update(rgba_data.data()); + sf::Sprite sprite{texture}; + sprite.setPosition(static_cast(rect.x), static_cast(rect.y)); + target_.draw(sprite); + sf::RectangleShape shape{{static_cast(rect.width), static_cast(rect.height)}}; + shape.setTexture(&texture); + target_.draw(shape); +} + } // namespace gfx diff --git a/gfx/sfml_canvas.h b/gfx/sfml_canvas.h index a896cfbf..7e9eda69 100644 --- a/gfx/sfml_canvas.h +++ b/gfx/sfml_canvas.h @@ -8,9 +8,11 @@ #include "gfx/icanvas.h" #include +#include #include #include +#include namespace sf { class Font; @@ -36,11 +38,13 @@ class SfmlCanvas : public ICanvas { void draw_rect(geom::Rect const &, Color const &, Borders const &, Corners const &) override; void draw_text(geom::Position, std::string_view, std::span, FontSize, FontStyle, Color) override; void draw_text(geom::Position, std::string_view, Font, FontSize, FontStyle, Color) override; + void draw_pixels(geom::Rect const &, std::span rgba_data) override; private: sf::RenderTarget &target_; sf::Shader border_shader_{}; std::map, std::less<>> font_cache_; + std::vector textures_; int scale_{1}; int tx_{0}; diff --git a/img/BUILD b/img/BUILD index e6a6b0b9..761cb495 100644 --- a/img/BUILD +++ b/img/BUILD @@ -74,6 +74,7 @@ cc_binary( ":gif", ":png", ":qoi", + "//gfx:sfml", "@sfml//:graphics", "@sfml//:window", ], diff --git a/img/img_example.cpp b/img/img_example.cpp index 8a02e6ee..5597d209 100644 --- a/img/img_example.cpp +++ b/img/img_example.cpp @@ -6,10 +6,9 @@ #include "img/png.h" #include "img/qoi.h" -#include +#include "gfx/sfml_canvas.h" + #include -#include -#include #include #include @@ -110,12 +109,7 @@ int main(int argc, char **argv) { window.setVerticalSyncEnabled(true); window.setActive(true); - sf::Image sf_image{}; - sf_image.create(width, height, bytes.data()); - sf::Texture texture{}; - texture.loadFromImage(sf_image); - sf::Sprite sprite{}; - sprite.setTexture(texture); + gfx::SfmlCanvas canvas{window}; bool running = true; while (running) { @@ -126,16 +120,15 @@ int main(int argc, char **argv) { running = false; break; case sf::Event::Resized: - window.setView(sf::View{sf::FloatRect{ - 0, 0, static_cast(event.size.width), static_cast(event.size.height)}}); + canvas.set_viewport_size(event.size.width, event.size.height); break; default: break; } } - window.clear(); - window.draw(sprite); + canvas.clear(gfx::Color{}); + canvas.draw_pixels({0, 0, static_cast(width), static_cast(height)}, bytes); window.display(); } }