From 4f2819bd81b2825dea4d6d7c0a11b8a9628285d5 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Fri, 26 Sep 2025 18:39:24 +0200 Subject: [PATCH] SVG + cmd line parsing --- .gitignore | 3 ++ CMakeLists.txt | 2 +- input_file.cpp | 13 +++--- input_file.hpp | 6 +++ main.cpp | 76 +++++++++++++++++++++++++++----- math/constants.hpp | 10 +++++ math/transforms.cpp | 1 + math/transforms.hpp | 2 - renderers/bitmap.hpp | 38 ++++++++++++++-- renderers/pgm_renderer.cpp | 90 ++++++++++++++++++++++---------------- renderers/pgm_renderer.hpp | 6 +-- renderers/renderer.hpp | 4 +- renderers/svg_renderer.cpp | 38 ++++++++++++++++ renderers/svg_renderer.hpp | 21 +++++++++ shapes/circle.cpp | 2 +- shapes/circle.hpp | 2 +- shapes/group.cpp | 33 -------------- shapes/group.hpp | 15 +++++-- shapes/line.cpp | 2 +- shapes/line.hpp | 2 +- shapes/rectangle.cpp | 2 +- shapes/rectangle.hpp | 2 +- shapes/shape.hpp | 2 +- 23 files changed, 264 insertions(+), 108 deletions(-) create mode 100644 math/constants.hpp create mode 100644 renderers/svg_renderer.cpp create mode 100644 renderers/svg_renderer.hpp delete mode 100644 shapes/group.cpp diff --git a/.gitignore b/.gitignore index 5ac174e..39767eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .cache/ .vscode/ build/ +*.txt +*.svg +*.pgm diff --git a/CMakeLists.txt b/CMakeLists.txt index 31627c5..ba37e21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ set(CMAKE_CXX_STANDARD 20) add_executable(drawing "math/transforms.cpp" "renderers/pgm_renderer.cpp" + "renderers/svg_renderer.cpp" "shapes/circle.cpp" - "shapes/group.cpp" "shapes/line.cpp" "shapes/rectangle.cpp" "input_file.cpp" diff --git a/input_file.cpp b/input_file.cpp index 1f1fdd4..22c0f6d 100644 --- a/input_file.cpp +++ b/input_file.cpp @@ -9,7 +9,9 @@ #include "shapes/line.hpp" #include "shapes/rectangle.hpp" -InputFile::InputFile(const std::filesystem::path& path) : m_file(path) +#include "math/constants.hpp" + +InputFile::InputFile(const std::filesystem::path& path) : m_file(path), m_cmdsProcessed(0) { if (m_file.bad()) { @@ -72,11 +74,6 @@ static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler }; } -static float DegToRad(float deg) -{ - return deg * 0.0174532925f; -} - shapes::Group InputFile::Parse() { shapes::Group shapes; @@ -108,7 +105,7 @@ shapes::Group InputFile::Parse() RegisterCommand(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); }); RegisterCommand( - cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, DegToRad(deg)); }); + cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD); }); RegisterCommand(cmds, "scale", [&shapes](int cx, int cy, float factor) { if (factor < std::numeric_limits().epsilon()) @@ -121,6 +118,7 @@ shapes::Group InputFile::Parse() while (std::getline(m_file, line)) { + // TODO: find # mid line if (line.empty() || line[0] == '#') continue; @@ -133,6 +131,7 @@ shapes::Group InputFile::Parse() throw std::runtime_error("Unknown command: " + cmdName); cmd->second(iss); + ++m_cmdsProcessed; } return shapes; diff --git a/input_file.hpp b/input_file.hpp index b26176c..456911f 100644 --- a/input_file.hpp +++ b/input_file.hpp @@ -14,6 +14,12 @@ class InputFile shapes::Group Parse(); + size_t GetNumProcessedCmds() const + { + return m_cmdsProcessed; + } + private: std::ifstream m_file; + size_t m_cmdsProcessed; }; diff --git a/main.cpp b/main.cpp index ad5decb..6dc0f42 100644 --- a/main.cpp +++ b/main.cpp @@ -1,29 +1,83 @@ #include #include +#include #include "input_file.hpp" #include "renderers/pgm_renderer.hpp" +#include "renderers/svg_renderer.hpp" -int main() +template +void Render(const shapes::Group& shapes, const std::filesystem::path& path, size_t width, size_t height) +{ + T renderer(path, width, height); + shapes.Draw(renderer); +} + +static size_t ParseDim(const char* start, const char* end) +{ + size_t val; + auto [ptr, ec] = std::from_chars(start, end, val); + + if (ec != std::errc()) + { + throw std::runtime_error("Cannot parse size"); + } + + return val; +} + +static std::tuple ParseSize(const std::string& sizeStr) +{ + auto xPos = sizeStr.find('x'); + + if (xPos == std::string::npos) + { + throw std::runtime_error("Size must be in format of x"); + } + + size_t width = ParseDim(sizeStr.data(), sizeStr.data() + xPos); + size_t height = ParseDim(sizeStr.data() + xPos + 1, sizeStr.data() + sizeStr.size()); + + return std::make_tuple(width, height); +} + +static void Run(const std::string& inputFile, const std::string& outputFile, const std::string& sizeStr) { try { - InputFile file("test.txt"); + auto [width, height] = ParseSize(sizeStr); - std::ofstream("here.txt"); + InputFile file(inputFile); + shapes::Group shapes = file.Parse(); - auto shapes = file.Parse(); - - PgmRenderer renderer("output.pgm", 300, 300); - shapes.Draw(renderer); - - renderer.Flush(); - - std::cout << "sjeta" << std::endl; + if (outputFile.ends_with(".svg")) + Render(shapes, outputFile, width, height); + else + Render(shapes, outputFile, width, height); + + std::cout << "OK" << std::endl; + std::cout << file.GetNumProcessedCmds() << std::endl; } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } } + +int main(int argc, char** argv) +{ + if (argc < 4) + { + const char* command = "drawing"; + + if (argc > 0) + command = argv[0]; + + std::cerr << "Usage: " << command << " " << std::endl; + return 1; + } + + Run(argv[1], argv[2], argv[3]); + return 0; +} diff --git a/math/constants.hpp b/math/constants.hpp new file mode 100644 index 0000000..75d4265 --- /dev/null +++ b/math/constants.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace math +{ + +constexpr float PI = 3.14159265358979323846f; +constexpr float DEG_TO_RAD = PI / 180.0f; +constexpr float RAD_TO_DEG = 1.0f / DEG_TO_RAD; + +} // namespace math diff --git a/math/transforms.cpp b/math/transforms.cpp index 771cee3..a24b4f3 100644 --- a/math/transforms.cpp +++ b/math/transforms.cpp @@ -1,5 +1,6 @@ #include "transforms.hpp" #include +#include "constants.hpp" math::Vector math::RotatePoint(const Vector& center, float angle, const Vector& p) { diff --git a/math/transforms.hpp b/math/transforms.hpp index 770c5ea..27f48ac 100644 --- a/math/transforms.hpp +++ b/math/transforms.hpp @@ -5,8 +5,6 @@ namespace math { -constexpr float PI = 3.14159265358979323846f; - Vector RotatePoint(const Vector& center, float angle, const Vector& p); Vector ScalePoint(const Vector& center, float factor, const Vector& p); float RotateAngle(float originalAngle, float rotationAngle); diff --git a/renderers/bitmap.hpp b/renderers/bitmap.hpp index 6bb218d..c6e155a 100644 --- a/renderers/bitmap.hpp +++ b/renderers/bitmap.hpp @@ -6,9 +6,32 @@ #include #include -struct Color +class Color { - uint8_t l{}; + public: + Color() : l{0} + { + } + + Color(uint8_t l) : l{l} + { + } + + uint8_t l{}; // luminence + + void Blend(const Color& src, uint8_t alpha) + { + l = BlendChannel(l, src.l, alpha); + // l = alpha; + } + + private: + static uint8_t BlendChannel(uint8_t a, uint8_t b, uint8_t alpha) + { + return static_cast((static_cast(a) * (255 - static_cast(alpha)) + + static_cast(b) * static_cast(alpha)) / + 255); + } }; class Bitmap @@ -43,12 +66,21 @@ class Bitmap { return m_width; } - + size_t GetHeight() const { return m_height; } + void Put(int x, int y, const Color& color, uint8_t alpha) + { + if (x < 0 || y < 0 || x >= m_width || y >= m_height) + return; // out of bounds + + (*this)[y][x].Blend(color, alpha); + + } + private: size_t m_width, m_height; std::vector m_data; diff --git a/renderers/pgm_renderer.cpp b/renderers/pgm_renderer.cpp index 30b15b0..b55b283 100644 --- a/renderers/pgm_renderer.cpp +++ b/renderers/pgm_renderer.cpp @@ -37,9 +37,15 @@ void PgmRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& siz void PgmRenderer::DrawCircle(const math::Vector& center, float radius) { + // TODO: cast RasterizeCircle(center.x, center.y, radius); } +PgmRenderer::~PgmRenderer() +{ + Flush(); +} + void PgmRenderer::Flush() { m_file << "P2" << std::endl; @@ -60,47 +66,57 @@ void PgmRenderer::Flush() void PgmRenderer::RasterizeLine(int x0, int y0, int x1, int y1) { - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; /* error value e_xy */ + // TODO: fix + const Color lineColor{0x00}; + float w = 2.0f; - for (;;) - { /* loop */ - m_bitmap[y0][x0].l = 0x00; - if (x0 == x1 && y0 == y1) - break; - e2 = 2 * err; - if (e2 >= dy) - { - err += dy; - x0 += sx; - } /* e_xy+e_x > 0 */ - if (e2 <= dx) - { - err += dx; - y0 += sy; - } /* e_xy+e_y < 0 */ + int dx = abs(x1 - x0); + int dy = abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx - dy; + + // perpendicular offsets for thickness + int ox = 0, oy = 0; + if (dx > dy) { + oy = 1; + } else { + ox = 1; + } + + while (1) { + // draw a square of pixels for thickness + for (int i = -w/2; i <= w/2; i++) { + for (int j = -w/2; j <= w/2; j++) { + m_bitmap.Put(x0 + i*ox, y0 + j*oy, lineColor, 0xFF); + } + } + + if (x0 == x1 && y0 == y1) break; + int e2 = 2 * err; + if (e2 > -dy) { err -= dy; x0 += sx; } + if (e2 < dx) { err += dx; y0 += sy; } } } -void PgmRenderer::RasterizeCircle(int xm, int ym, int r) +void PgmRenderer::RasterizeCircle(int cx, int cy, int r) { - int x = -r, y = 0, err = 2 - 2 * r; /* II. Quadrant */ - do - { - m_bitmap[ym + y][xm - x].l = 0x00; - m_bitmap[ym - x][xm - y].l = 0x00; - m_bitmap[ym - y][xm + x].l = 0x00; - m_bitmap[ym + x][xm + y].l = 0x00; + // TODO: fix - // setPixel(xm - x, ym + y); /* I. Quadrant */ - // setPixel(xm - y, ym - x); /* II. Quadrant */ - // setPixel(xm + x, ym - y); /* III. Quadrant */ - // setPixel(xm + y, ym + x); /* IV. Quadrant */ - r = err; - if (r <= y) - err += ++y * 2 + 1; /* e_xy+e_y < 0 */ - if (r > x || err > y) - err += ++x * 2 + 1; /* e_xy+e_x > 0 or no 2nd y-step */ - } while (x < 0); + const Color circleColor{0x00}; + float w = 2.0f; + + + int r_outer = r + w / 2; + int r_inner = r - w / 2; + if (r_inner < 0) r_inner = 0; + + for (int y = -r_outer; y <= r_outer; y++) { + for (int x = -r_outer; x <= r_outer; x++) { + int dist2 = x*x + y*y; + if (dist2 <= r_outer*r_outer && dist2 >= r_inner*r_inner) { + m_bitmap.Put(cx + x, cy + y, circleColor, 0xFF); + } + } + } } diff --git a/renderers/pgm_renderer.hpp b/renderers/pgm_renderer.hpp index afa23b1..785e646 100644 --- a/renderers/pgm_renderer.hpp +++ b/renderers/pgm_renderer.hpp @@ -16,9 +16,7 @@ class PgmRenderer : public Renderer void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) override; void DrawCircle(const math::Vector& center, float radius) override; - void Flush() override; - - ~PgmRenderer() override = default; + ~PgmRenderer() override; private: std::ofstream m_file; @@ -26,4 +24,6 @@ class PgmRenderer : public Renderer void RasterizeLine(int x0, int y0, int x1, int y1); void RasterizeCircle(int xm, int ym, int r); + + void Flush(); }; \ No newline at end of file diff --git a/renderers/renderer.hpp b/renderers/renderer.hpp index d57dd4a..e0706f5 100644 --- a/renderers/renderer.hpp +++ b/renderers/renderer.hpp @@ -8,7 +8,9 @@ class Renderer virtual void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) = 0; virtual void DrawCircle(const math::Vector& center, float radius) = 0; - virtual void Flush() = 0; + virtual void Flush() + { + } virtual ~Renderer() = default; }; diff --git a/renderers/svg_renderer.cpp b/renderers/svg_renderer.cpp new file mode 100644 index 0000000..e2c33d1 --- /dev/null +++ b/renderers/svg_renderer.cpp @@ -0,0 +1,38 @@ +#include "svg_renderer.hpp" + +#include "math/constants.hpp" + +SvgRenderer::SvgRenderer(const std::filesystem::path& path, size_t width, size_t height) : m_file(path) +{ + if (m_file.bad()) + throw std::runtime_error("Cannot open " + path.string() + " for writing"); + + m_file << "" << std::endl; +} + +void SvgRenderer::DrawLine(const math::Vector& p0, const math::Vector& p1) +{ + m_file << " " << std::endl; +} + +void SvgRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) +{ + float angleDeg = angle * math::RAD_TO_DEG; + + m_file << " " << std::endl; +} + +void SvgRenderer::DrawCircle(const math::Vector& center, float radius) +{ + m_file << " " << std::endl; +} + +SvgRenderer::~SvgRenderer() +{ + m_file << "" << std::endl; +} diff --git a/renderers/svg_renderer.hpp b/renderers/svg_renderer.hpp new file mode 100644 index 0000000..a2bfd99 --- /dev/null +++ b/renderers/svg_renderer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "renderer.hpp" + +#include +#include + +class SvgRenderer : public Renderer +{ + public: + SvgRenderer(const std::filesystem::path& path, size_t width, size_t height); + + void DrawLine(const math::Vector& p0, const math::Vector& p1) override; + void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) override; + void DrawCircle(const math::Vector& center, float radius) override; + + ~SvgRenderer() override; + + private: + std::ofstream m_file; +}; \ No newline at end of file diff --git a/shapes/circle.cpp b/shapes/circle.cpp index 4eea6c5..0d46105 100644 --- a/shapes/circle.cpp +++ b/shapes/circle.cpp @@ -18,7 +18,7 @@ void shapes::Circle::Scale(const math::Vector& center, float factor) m_radius *= factor; } -void shapes::Circle::Draw(Renderer& renderer) +void shapes::Circle::Draw(Renderer& renderer) const { renderer.DrawCircle(m_center, m_radius); } diff --git a/shapes/circle.hpp b/shapes/circle.hpp index 2ce350b..da6b3ca 100644 --- a/shapes/circle.hpp +++ b/shapes/circle.hpp @@ -26,7 +26,7 @@ class Circle : public Shape void Rotate(const math::Vector& center, float angle) override; void Scale(const math::Vector& center, float factor) override; - void Draw(Renderer& renderer) override; + void Draw(Renderer& renderer) const override; ~Circle() override = default; diff --git a/shapes/group.cpp b/shapes/group.cpp deleted file mode 100644 index 289c579..0000000 --- a/shapes/group.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "group.hpp" - -// void shapes::Group::Translate(const math::Vector& offset) -// { -// for (const auto& shape : m_shapes) -// { -// shape->Translate(offset); -// } -// } - -// void shapes::Group::Rotate(const math::Vector& center, float angle) -// { -// for (const auto& shape : m_shapes) -// { -// shape->Rotate(center, angle); -// } -// } - -// void shapes::Group::Scale(const math::Vector& center, float factor) -// { -// for (const auto& shape : m_shapes) -// { -// shape->Scale(center, factor); -// } -// } - -// void shapes::Group::Draw(Renderer& renderer) -// { -// for (const auto& shape : m_shapes) -// { -// shape->Draw(renderer); -// } -// } diff --git a/shapes/group.hpp b/shapes/group.hpp index 1a05af0..432e064 100644 --- a/shapes/group.hpp +++ b/shapes/group.hpp @@ -13,12 +13,13 @@ class Group : public Shape public: Group() = default; - Group(Group&& other) + Group(Group&& other) : m_shapes{std::move(other.m_shapes)} { } - + Group& operator=(Group&& other) { + // TODO: overit m_shapes = std::move(other.m_shapes); return *this; } @@ -43,7 +44,7 @@ class Group : public Shape ShapesCall<&Shape::Scale>(center, factor); } - void Draw(Renderer& renderer) override + void Draw(Renderer& renderer) const override { ShapesCall<&Shape::Draw>(renderer); } @@ -60,6 +61,14 @@ class Group : public Shape (shape.get()->*TFun)(std::forward(args)...); } } + + template void ShapesCall(TArgs&&... args) const + { + for (const auto& shape : m_shapes) + { + (shape.get()->*TFun)(std::forward(args)...); + } + } }; } // namespace shapes \ No newline at end of file diff --git a/shapes/line.cpp b/shapes/line.cpp index 9526fe5..016726c 100644 --- a/shapes/line.cpp +++ b/shapes/line.cpp @@ -20,7 +20,7 @@ void shapes::Line::Scale(const math::Vector& center, float factor) m_p1 = math::ScalePoint(center, factor, m_p1); } -void shapes::Line::Draw(Renderer& renderer) +void shapes::Line::Draw(Renderer& renderer) const { renderer.DrawLine(m_p0, m_p1); } diff --git a/shapes/line.hpp b/shapes/line.hpp index 847c318..d1e36ce 100644 --- a/shapes/line.hpp +++ b/shapes/line.hpp @@ -17,7 +17,7 @@ class Line : public Shape void Rotate(const math::Vector& center, float angle) override; void Scale(const math::Vector& center, float factor) override; - void Draw(Renderer& renderer) override; + void Draw(Renderer& renderer) const override; private: math::Vector m_p0; diff --git a/shapes/rectangle.cpp b/shapes/rectangle.cpp index fd5c94a..a300089 100644 --- a/shapes/rectangle.cpp +++ b/shapes/rectangle.cpp @@ -19,7 +19,7 @@ void shapes::Rectangle::Scale(const math::Vector& center, float factor) m_size *= factor; } -void shapes::Rectangle::Draw(Renderer& renderer) +void shapes::Rectangle::Draw(Renderer& renderer) const { renderer.DrawRectangle(m_pos, m_size, m_angle); } diff --git a/shapes/rectangle.hpp b/shapes/rectangle.hpp index ca5e18b..8f90306 100644 --- a/shapes/rectangle.hpp +++ b/shapes/rectangle.hpp @@ -32,7 +32,7 @@ class Rectangle : public Shape void Rotate(const math::Vector& center, float angle) override; void Scale(const math::Vector& center, float factor) override; - void Draw(Renderer& renderer) override; + void Draw(Renderer& renderer) const override; ~Rectangle() override = default; diff --git a/shapes/shape.hpp b/shapes/shape.hpp index 05ffda6..05fe6f0 100644 --- a/shapes/shape.hpp +++ b/shapes/shape.hpp @@ -16,7 +16,7 @@ class Shape virtual void Rotate(const math::Vector& center, float angle) = 0; virtual void Scale(const math::Vector& center, float factor) = 0; - virtual void Draw(Renderer& renderer) = 0; + virtual void Draw(Renderer& renderer) const = 0; virtual ~Shape() = default; };