skullc-peripherals/Utility/Inc/utility_pixelbuffer.hpp
erki ea99a8a6ba
All checks were successful
continuous-integration/drone/push Build is passing
gitea/skullc-peripherals/pipeline/head This commit looks good
Other: fix ./clang-format to apply new-lines for else statements properly
2022-12-10 17:15:55 +02:00

308 lines
7.2 KiB
C++

//
// Created by erki on 13.05.21.
//
#ifndef SKULLC_UTILITY_PIXELBUFFER_HPP
#define SKULLC_UTILITY_PIXELBUFFER_HPP
#include <array>
#include <cmath>
#include <cstdint>
#include <string_view>
namespace Utility
{
template<std::size_t W, std::size_t H>
class PixelBuffer
{
public:
static constexpr std::size_t width = W;
static constexpr std::size_t height = H;
using value = std::uint8_t;
using container = std::array<value, width * height>;
using iterator = typename container::iterator;
using const_iterator = typename container::const_iterator;
using coordinates = std::pair<std::size_t, std::size_t>;
PixelBuffer()
: data_({0})
{}
explicit PixelBuffer(const value& fill_value)
: data_()
{
fill(fill_value);
}
constexpr value& at(const std::size_t& x, const std::size_t& y)
{
return data_[x + ((y) *width)];
}
constexpr const value& at(const std::size_t& x, const std::size_t& y) const
{
return data_[x + ((y) *width)];
}
constexpr value& at(const coordinates& c)
{
return at(x_(c), y_(c));
}
constexpr const value& at(const coordinates& c) const
{
return at(x_(c), y_(c));
}
const container& data() const
{
return data_;
}
const value* ptr() const
{
return data_.data();
}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator cbegin() const
{
return data_.cbegin();
}
const_iterator cend() const
{
return data_.cend();
}
void fill(const value& color)
{
data_.fill(color);
}
/**
* Draws the text in the font provided onto the pixel buffer.
*
* Currently only supports fonts which are 1 byte high at most.
*
* @todo: make the function work with multi-byte fonts.
*
* @tparam Font The font object to be used. Must have the following members:
* * height, indicating how many pixels tall the font is.
* * width, indicating how many pixels wide the font is.
* * getCharacterBytes(char) that returns start and stop iterators for a given character.
*/
template<typename Font>
void text(const std::string_view& text, const coordinates& start, const value& color, const value& background = 0x00)
{
std::uint32_t x = x_(start);
const std::uint32_t y = y_(start);
for (const char& c : text)
{
const auto [c_start, c_stop] = Font::getCharacterBytes(c);
for (auto it = c_start; it != c_stop; it++, x++)
{
const std::uint8_t& column_byte = *it;
if (x >= width)
return;
for (std::uint32_t i = 0; i < Font::height && i + y < height; i++)
{
at(x, y + i) = column_byte & (1 << i) ? color : background;
}
}
x++;
}
}
void rectangle(const coordinates& top_left, const coordinates& size, const value& color)
{
for (auto i = x_(top_left); i < (x_(top_left) + x_(size)); i++)
{
at(i, y_(top_left)) = color;
at(i, y_(top_left) + y_(size) - 1) = color;
}
for (auto i = y_(top_left); i < (y_(top_left) + y_(size)); i++)
{
at(x_(top_left), i) = color;
at(x_(top_left) + x_(size) - 1, i) = color;
}
}
void line(const coordinates& p1, const coordinates& p2, const value& color)
{
const coordinates start = {std::min(x_(p1), x_(p2)), std::min(y_(p1), y_(p2))};
const coordinates stop = {std::max(x_(p1), x_(p2)), std::max(y_(p1), y_(p2))};
const auto dx = std::int32_t(x_(stop)) - std::int32_t(x_(start));
const auto dy = std::int32_t(y_(stop)) - std::int32_t(y_(start));
auto x = std::int32_t(x_(start));
auto y = std::int32_t(y_(start));
// We shortcut the direct lines, as Bresenham's algorithm cannot handle them.
if (dx == 0 && dy == 0)
{
at(start) = color;
}
else if (dx == 0)
{
for (; y < y_(stop); y++)
at({x, y}) = color;
}
else if (dy == 0)
{
for (; x < x_(stop); x++)
at({x, y}) = color;
}
else
{
// Bresenham's algorithm.
std::int32_t p = 2 * dy - dx;
while (x < x_(stop))
{
at({x, y}) = color;
if (p >= 0)
{
y++;
p = p + 2 * dy - 2 * dx;
}
else
{
p = p + 2 * dy;
}
x++;
}
}
}
void circle(const coordinates& center, const std::size_t radius, const value& color)
{
auto draw_pixels = [this, color, center](const std::size_t& x, const std::size_t& y) {
at(x_(center) + x, y_(center) + y) = color;
at(x_(center) - x, y_(center) + y) = color;
at(x_(center) + x, y_(center) - y) = color;
at(x_(center) - x, y_(center) - y) = color;
at(x_(center) + y, y_(center) + x) = color;
at(x_(center) - y, y_(center) + x) = color;
at(x_(center) + y, y_(center) - x) = color;
at(x_(center) - y, y_(center) - x) = color;
};
auto x = std::int32_t(0);
auto y = std::int32_t(radius);
auto d = 3 - 2 * std::int32_t(radius);
draw_pixels(x, y);
while (y >= x)
{
x++;
if (d > 0)
{
y--;
d = d + 4 * (x - y) + 10;
}
else
{
d = d + 4 * x + 6;
}
draw_pixels(x, y);
}
}
template<std::size_t VW, std::size_t VH, typename TP>
struct View
{
constexpr static std::size_t width = VW;
constexpr static std::size_t height = VH;
using parent_type = std::decay_t<TP>;
using value = typename parent_type::value;
using coordinates = typename parent_type::coordinates;
static_assert(width <= parent_type::width, "Parent type width is smaller than view's width.");
static_assert(height <= parent_type::height, "Parent type height is smaller than view's height.");
coordinates offset;
parent_type& parent;
View() = delete;
explicit View(const coordinates& offset, parent_type& buffer)
: offset(offset), parent(buffer)
{}
constexpr value& at(const std::size_t& x, const std::size_t& y)
{
return parent.at(parent.x_(offset) + x, parent.y_(offset) + y);
}
constexpr const value& at(const std::size_t& x, const std::size_t& y) const
{
return parent.at(parent.x_(offset) + x, parent.y_(offset) + y);
}
constexpr value& at(const coordinates& c)
{
return parent.at(parent.x_(offset) + parent.x_(c), parent.y_(offset) + parent.y_(c));
}
constexpr const value& at(const coordinates& c) const
{
return parent.at(parent.x_(offset) + parent.x_(c), parent.y_(offset) + parent.y_(c));
}
template<std::size_t VW_, std::size_t VH_>
auto view(const coordinates& offset)
{
return View<VW_, VH_, parent_type>(offset, parent);
}
};
template<std::size_t VW, std::size_t VH, typename TP>
friend struct View;
template<std::size_t VW, std::size_t VH>
auto view(const coordinates& offset) -> View<VW, VH, decltype(*this)>
{
using rtype = View<VW, VH, decltype(*this)>;
return rtype(offset, *this);
}
private:
container data_;
constexpr const std::size_t& x_(const coordinates& c) const
{
return c.first;
}
constexpr const std::size_t& y_(const coordinates& c) const
{
return c.second;
}
};
}// namespace Utility
#endif//SKULLC_UTILITY_PIXELBUFFER_HPP