It's drawing

This commit is contained in:
tovjemam 2025-09-26 15:42:44 +02:00
parent d508045ec3
commit 9b517b6aee
23 changed files with 499 additions and 175 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.cache/
.vscode/
build/

View File

@ -5,13 +5,14 @@ project(CppDrawing)
set(CMAKE_CXX_STANDARD 20)
add_executable(drawing
"contexts/pgm_drawing_context.cpp"
"math/transforms.cpp"
"primitives/line.cpp"
"primitives/rectangle.cpp"
"primitives/circle.cpp"
"main.cpp"
"renderers/pgm_renderer.cpp"
"shapes/circle.cpp"
"shapes/group.cpp"
"shapes/line.cpp"
"shapes/rectangle.cpp"
"input_file.cpp"
"main.cpp"
)
target_include_directories(drawing PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,60 +0,0 @@
#include "pgm_drawing_context.hpp"
#include <cmath>
PgmDrawingContext::PgmDrawingContext(const std::filesystem::path& path, size_t width, size_t height)
: m_bitmap(width, height, Color{0xFF})
{
}
void PgmDrawingContext::DrawLine(const math::Vector& p0, const math::Vector& p1)
{
RasterizeLine(static_cast<int>(p0.x), static_cast<int>(p0.y), static_cast<int>(p1.x), static_cast<int>(p1.y));
}
void PgmDrawingContext::DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle)
{
const float sina = std::sin(angle);
const float cosa = std::cos(angle);
const math::Vector bX{cosa, sina};
const math::Vector bY{-sina, cosa};
const auto& pD = pos;
const auto pA = pD + bY * size.y;
const auto pB = pA + bX * size.x;
const auto pC = pD + bX * size.x;
PgmDrawingContext::DrawLine(pA, pB);
PgmDrawingContext::DrawLine(pB, pC);
PgmDrawingContext::DrawLine(pC, pD);
PgmDrawingContext::DrawLine(pD, pA);
}
void PgmDrawingContext::DrawCircle(const math::Vector& center, float radius)
{
}
void PgmDrawingContext::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 */
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 */
}
}

View File

@ -1,2 +1,139 @@
#include "input_file.hpp"
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include "shapes/circle.hpp"
#include "shapes/line.hpp"
#include "shapes/rectangle.hpp"
InputFile::InputFile(const std::filesystem::path& path) : m_file(path)
{
if (m_file.bad())
{
throw std::runtime_error("Cannot open " + path.string() + " for reading");
}
}
static std::istream& operator>>(std::istream& is, math::Vector& vec)
{
return is >> vec.x >> vec.y;
}
using CommandMap = std::map<std::string, std::function<void(std::istream&)>>;
template <class T> static const char* GetTypeName()
{
return typeid(T).name();
}
template <> static const char* GetTypeName<int>()
{
return "integer";
}
template <> static const char* GetTypeName<float>()
{
return "float";
}
template <> static const char* GetTypeName<math::Vector>()
{
return "vector";
}
template <> static const char* GetTypeName<std::string>()
{
return "string";
}
template <class T> static T ReadVal(std::istream& is)
{
T v;
is >> v;
// std::cout << "Reading " << GetTypeName<T>() << ' ' << v << std::endl;
if (is.bad())
{
throw std::runtime_error("Could not parse " + std::string(GetTypeName<T>()) + " from stream");
}
return v;
}
template <class... TArgs, class THandler>
static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler&& handler)
{
cmdMap[cmd] = [handler = std::forward<THandler>(handler)](std::istream& is) {
std::apply(handler, std::tuple<TArgs...>{ReadVal<TArgs>(is)...}); // pres tuple kvuli poradi evaluace...
};
}
static float DegToRad(float deg)
{
return deg * 0.0174532925f;
}
shapes::Group InputFile::Parse()
{
shapes::Group shapes;
CommandMap cmds;
/* SHAPES */
RegisterCommand<int, int, int, int>(cmds, "line", [&shapes](int x1, int y1, int x2, int y2) {
shapes.AddShape<shapes::Line>(math::Vector{x1, y1}, math::Vector{x2, y2});
});
RegisterCommand<int, int, int>(cmds, "circle", [&shapes](int x, int y, int r) {
if (r <= 0)
throw std::runtime_error("Circle radius must be a positive integer");
shapes.AddShape<shapes::Circle>(math::Vector{x, y}, static_cast<float>(r));
});
RegisterCommand<int, int, int, int>(cmds, "rect", [&shapes](int x, int y, int w, int h) {
if (w <= 0 || h <= 0)
throw std::runtime_error("Rect dimensions must be positive integers");
shapes.AddShape<shapes::Rectangle>(math::Vector{x, y}, math::Vector{w, h});
});
/* TRANSFORMS */
RegisterCommand<int, int>(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); });
RegisterCommand<int, int, float>(
cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, DegToRad(deg)); });
RegisterCommand<int, int, float>(cmds, "scale", [&shapes](int cx, int cy, float factor) {
if (factor < std::numeric_limits<decltype(factor)>().epsilon())
throw std::runtime_error("Scale factor must be a non-zero real number");
shapes.Scale(math::Vector{cx, cy}, factor);
});
std::string line;
while (std::getline(m_file, line))
{
if (line.empty() || line[0] == '#')
continue;
std::istringstream iss(line);
std::string cmdName = ReadVal<std::string>(iss);
auto cmd = cmds.find(cmdName);
if (cmd == cmds.end())
throw std::runtime_error("Unknown command: " + cmdName);
cmd->second(iss);
}
return shapes;
}

View File

@ -2,15 +2,17 @@
#include <filesystem>
#include <fstream>
#include <memory>
#include <vector>
#include "drawing_context.hpp"
#include "shapes/group.hpp"
class InputFile
{
public:
InputFile(const std::filesystem::path& path);
void Parse(DrawingContext& ctx);
shapes::Group Parse();
private:
std::ifstream m_file;

View File

@ -1,8 +1,29 @@
#include <iostream>
#include "pgm_drawing_context.hpp"
#include <stdexcept>
#include "input_file.hpp"
#include "renderers/pgm_renderer.hpp"
int main()
{
std::cout << "sjeta" << std::endl;
try
{
InputFile file("test.txt");
std::ofstream("here.txt");
auto shapes = file.Parse();
PgmRenderer renderer("output.pgm", 300, 300);
shapes.Draw(renderer);
renderer.Flush();
std::cout << "sjeta" << std::endl;
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
}

View File

@ -16,6 +16,10 @@ class Vector
{
}
Vector(int x, int y) : x{static_cast<float>(x)}, y{static_cast<float>(y)}
{
}
// float& operator[](size_t idx) { return m_v[idx]; }
// const float& operator[](size_t idx) const { return m_v[idx]; }

View File

@ -1,24 +0,0 @@
#include "circle.hpp"
#include "math/transforms.hpp"
#include "contexts/drawing_context.hpp"
void primitives::Circle::Translate(const math::Vector& offset)
{
m_center += offset;
}
void primitives::Circle::Rotate(const math::Vector& center, float angle)
{
m_center = math::RotatePoint(center, angle, m_center);
}
void primitives::Circle::Scale(const math::Vector& center, float factor)
{
m_center = math::ScalePoint(center, factor, m_center);
m_radius *= factor;
}
void primitives::Circle::Draw(DrawingContext& ctx)
{
ctx.DrawCircle(m_center, m_radius);
}

View File

@ -1,26 +0,0 @@
#include "line.hpp"
#include "math/transforms.hpp"
#include "contexts/drawing_context.hpp"
void primitives::Line::Translate(const math::Vector& offset)
{
m_p0 += offset;
m_p1 += offset;
}
void primitives::Line::Rotate(const math::Vector& center, float angle)
{
m_p0 = math::RotatePoint(center, angle, m_p0);
m_p1 = math::RotatePoint(center, angle, m_p1);
}
void primitives::Line::Scale(const math::Vector& center, float factor)
{
m_p0 = math::ScalePoint(center, factor, m_p0);
m_p1 = math::ScalePoint(center, factor, m_p1);
}
void primitives::Line::Draw(DrawingContext& ctx)
{
ctx.DrawLine(m_p0, m_p1);
}

View File

@ -1,25 +0,0 @@
#include "rectangle.hpp"
#include "math/transforms.hpp"
#include "contexts/drawing_context.hpp"
void primitives::Rectangle::Translate(const math::Vector& offset)
{
m_pos += offset;
}
void primitives::Rectangle::Rotate(const math::Vector& center, float angle)
{
m_pos = math::RotatePoint(center, angle, m_pos);
m_angle = math::RotateAngle(m_angle, angle);
}
void primitives::Rectangle::Scale(const math::Vector& center, float factor)
{
m_pos = math::ScalePoint(center, factor, m_pos);
m_size *= factor;
}
void primitives::Rectangle::Draw(DrawingContext& ctx)
{
ctx.DrawRectangle(m_pos, m_size, m_angle);
}

View File

@ -23,7 +23,7 @@ class Bitmap
// {
// return {&m_data[row * m_width], m_width};
// };
// std::span<const Color> operator[](size_t row) const
// {
// return {&m_data[row * m_width], m_width};
@ -33,12 +33,22 @@ class Bitmap
{
return &m_data[row * m_width];
};
const Color* operator[](size_t row) const
{
return &m_data[row * m_width];
};
size_t GetWidth() const
{
return m_width;
}
size_t GetHeight() const
{
return m_height;
}
private:
size_t m_width, m_height;
std::vector<Color> m_data;

106
renderers/pgm_renderer.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "pgm_renderer.hpp"
#include <cmath>
#include <stdexcept>
PgmRenderer::PgmRenderer(const std::filesystem::path& path, size_t width, size_t height)
: m_file(path), m_bitmap(width, height, Color{0xFF})
{
if (m_file.bad())
{
throw std::runtime_error("Could not open file for writing: " + path.string());
}
}
void PgmRenderer::DrawLine(const math::Vector& p0, const math::Vector& p1)
{
RasterizeLine(static_cast<int>(p0.x), static_cast<int>(p0.y), static_cast<int>(p1.x), static_cast<int>(p1.y));
}
void PgmRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle)
{
const float sina = std::sin(angle);
const float cosa = std::cos(angle);
const math::Vector bX{cosa, sina};
const math::Vector bY{-sina, cosa};
const auto& pD = pos;
const auto pA = pD + bY * size.y;
const auto pB = pA + bX * size.x;
const auto pC = pD + bX * size.x;
PgmRenderer::DrawLine(pA, pB);
PgmRenderer::DrawLine(pB, pC);
PgmRenderer::DrawLine(pC, pD);
PgmRenderer::DrawLine(pD, pA);
}
void PgmRenderer::DrawCircle(const math::Vector& center, float radius)
{
RasterizeCircle(center.x, center.y, radius);
}
void PgmRenderer::Flush()
{
m_file << "P2" << std::endl;
m_file << "# KIV/CPP" << std::endl;
m_file << m_bitmap.GetWidth() << ' ' << m_bitmap.GetHeight() << std::endl;
m_file << 255 << std::endl;
for (size_t y = 0; y < m_bitmap.GetHeight(); ++y)
{
for (size_t x = 0; x < m_bitmap.GetWidth(); ++x)
{
m_file << static_cast<int>(m_bitmap[y][x].l) << ' ';
}
m_file << std::endl;
}
}
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 */
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 */
}
}
void PgmRenderer::RasterizeCircle(int xm, int ym, 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;
// 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);
}

View File

@ -1,14 +1,16 @@
#pragma once
#include "bitmap.hpp"
#include "drawing_context.hpp"
#include <cstddef>
#include <filesystem>
#include <fstream>
class PgmDrawingContext : public DrawingContext
#include "renderer.hpp"
#include "bitmap.hpp"
class PgmRenderer : public Renderer
{
public:
PgmDrawingContext(const std::filesystem::path& path, size_t width, size_t height);
PgmRenderer(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;
@ -16,10 +18,12 @@ class PgmDrawingContext : public DrawingContext
void Flush() override;
~PgmDrawingContext() override = default;
~PgmRenderer() override = default;
private:
std::ofstream m_file;
Bitmap m_bitmap;
void RasterizeLine(int x0, int y0, int x1, int y1);
void RasterizeCircle(int xm, int ym, int r);
};

View File

@ -1,7 +1,7 @@
#pragma once
#include "math/vector.hpp"
class DrawingContext
class Renderer
{
public:
virtual void DrawLine(const math::Vector& p0, const math::Vector& p1) = 0;
@ -10,5 +10,5 @@ class DrawingContext
virtual void Flush() = 0;
virtual ~DrawingContext() = 0;
virtual ~Renderer() = default;
};

24
shapes/circle.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "circle.hpp"
#include "math/transforms.hpp"
#include "renderers/renderer.hpp"
void shapes::Circle::Translate(const math::Vector& offset)
{
m_center += offset;
}
void shapes::Circle::Rotate(const math::Vector& center, float angle)
{
m_center = math::RotatePoint(center, angle, m_center);
}
void shapes::Circle::Scale(const math::Vector& center, float factor)
{
m_center = math::ScalePoint(center, factor, m_center);
m_radius *= factor;
}
void shapes::Circle::Draw(Renderer& renderer)
{
renderer.DrawCircle(m_center, m_radius);
}

View File

@ -1,11 +1,11 @@
#pragma once
#include "primitive.hpp"
#include "shape.hpp"
namespace primitives
namespace shapes
{
class Circle : public Primitive
class Circle : public Shape
{
public:
Circle(const math::Vector& center, float radius) : m_center(center), m_radius(radius)
@ -26,7 +26,7 @@ class Circle : public Primitive
void Rotate(const math::Vector& center, float angle) override;
void Scale(const math::Vector& center, float factor) override;
void Draw(DrawingContext& ctx) override;
void Draw(Renderer& renderer) override;
~Circle() override = default;
@ -35,4 +35,4 @@ class Circle : public Primitive
float m_radius;
};
} // namespace primitives
} // namespace shapes

33
shapes/group.cpp Normal file
View File

@ -0,0 +1,33 @@
#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);
// }
// }

65
shapes/group.hpp Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include "shape.hpp"
#include <memory>
#include <vector>
namespace shapes
{
class Group : public Shape
{
public:
Group() = default;
Group(Group&& other)
{
}
Group& operator=(Group&& other)
{
m_shapes = std::move(other.m_shapes);
return *this;
}
template <class TShape, class... TArgs> void AddShape(TArgs&&... args)
{
m_shapes.emplace_back(std::make_unique<TShape>(std::forward<TArgs>(args)...));
}
void Translate(const math::Vector& offset) override
{
ShapesCall<&Shape::Translate>(offset);
}
void Rotate(const math::Vector& center, float angle) override
{
ShapesCall<&Shape::Rotate>(center, angle);
}
void Scale(const math::Vector& center, float factor) override
{
ShapesCall<&Shape::Scale>(center, factor);
}
void Draw(Renderer& renderer) override
{
ShapesCall<&Shape::Draw>(renderer);
}
~Group() override = default;
private:
std::vector<std::unique_ptr<Shape>> m_shapes;
template <auto TFun, class... TArgs> void ShapesCall(TArgs&&... args)
{
for (auto& shape : m_shapes)
{
(shape.get()->*TFun)(std::forward<TArgs>(args)...);
}
}
};
} // namespace shapes

26
shapes/line.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "line.hpp"
#include "math/transforms.hpp"
#include "renderers/renderer.hpp"
void shapes::Line::Translate(const math::Vector& offset)
{
m_p0 += offset;
m_p1 += offset;
}
void shapes::Line::Rotate(const math::Vector& center, float angle)
{
m_p0 = math::RotatePoint(center, angle, m_p0);
m_p1 = math::RotatePoint(center, angle, m_p1);
}
void shapes::Line::Scale(const math::Vector& center, float factor)
{
m_p0 = math::ScalePoint(center, factor, m_p0);
m_p1 = math::ScalePoint(center, factor, m_p1);
}
void shapes::Line::Draw(Renderer& renderer)
{
renderer.DrawLine(m_p0, m_p1);
}

View File

@ -1,12 +1,12 @@
#pragma once
#include "math/vector.hpp"
#include "primitive.hpp"
#include "shape.hpp"
namespace primitives
namespace shapes
{
class Line : public Primitive
class Line : public Shape
{
public:
Line(const math::Vector& p0, const math::Vector& p1) : m_p0(p0), m_p1(p1)
@ -17,11 +17,11 @@ class Line : public Primitive
void Rotate(const math::Vector& center, float angle) override;
void Scale(const math::Vector& center, float factor) override;
void Draw(DrawingContext& ctx) override;
void Draw(Renderer& renderer) override;
private:
math::Vector m_p0;
math::Vector m_p1;
};
} // namespace primitives
} // namespace shapes

25
shapes/rectangle.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "rectangle.hpp"
#include "math/transforms.hpp"
#include "renderers/renderer.hpp"
void shapes::Rectangle::Translate(const math::Vector& offset)
{
m_pos += offset;
}
void shapes::Rectangle::Rotate(const math::Vector& center, float angle)
{
m_pos = math::RotatePoint(center, angle, m_pos);
m_angle = math::RotateAngle(m_angle, angle);
}
void shapes::Rectangle::Scale(const math::Vector& center, float factor)
{
m_pos = math::ScalePoint(center, factor, m_pos);
m_size *= factor;
}
void shapes::Rectangle::Draw(Renderer& renderer)
{
renderer.DrawRectangle(m_pos, m_size, m_angle);
}

View File

@ -1,12 +1,12 @@
#pragma once
#include "math/vector.hpp"
#include "primitive.hpp"
#include "shape.hpp"
namespace primitives
namespace shapes
{
class Rectangle : public Primitive
class Rectangle : public Shape
{
public:
Rectangle(const math::Vector& pos, const math::Vector& size) : m_pos(pos), m_size(size), m_angle(0.0f)
@ -32,7 +32,7 @@ class Rectangle : public Primitive
void Rotate(const math::Vector& center, float angle) override;
void Scale(const math::Vector& center, float factor) override;
void Draw(DrawingContext& ctx) override;
void Draw(Renderer& renderer) override;
~Rectangle() override = default;
@ -42,4 +42,4 @@ class Rectangle : public Primitive
float m_angle;
};
} // namespace primitives
} // namespace shapes

View File

@ -2,23 +2,23 @@
#include "math/vector.hpp"
class DrawingContext;
class Renderer;
namespace primitives
namespace shapes
{
class Primitive
class Shape
{
public:
Primitive() = default;
Shape() = default;
virtual void Translate(const math::Vector& offset) = 0;
virtual void Rotate(const math::Vector& center, float angle) = 0;
virtual void Scale(const math::Vector& center, float factor) = 0;
virtual void Draw(DrawingContext& ctx) = 0;
virtual void Draw(Renderer& renderer) = 0;
virtual ~Primitive() = 0;
virtual ~Shape() = default;
};
} // namespace primitives
} // namespace shapes