diff --git a/source/simple/graphical/pixels.cpp b/source/simple/graphical/pixels.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ffe5ade7c72da500360aa69234a433fdbdaa964b --- /dev/null +++ b/source/simple/graphical/pixels.cpp @@ -0,0 +1,41 @@ +#include "pixels.hpp" + +namespace simple::graphical +{ + namespace pixel_view_details + { + template class impl<tag::writer, pixel_byte, pixel_byte>; + template class impl<tag::reader, pixel_byte, pixel_byte>; + template class impl<tag::writer, uint16_t, pixel_byte>; + template class impl<tag::reader, uint16_t, pixel_byte>; + template class impl<tag::writer, rgb_pixel, pixel_byte>; + template class impl<tag::reader, rgb_pixel, pixel_byte>; + template class impl<tag::writer, rgba_pixel, pixel_byte>; + template class impl<tag::reader, rgba_pixel, pixel_byte>; + } // namespace pixel_view_details + + template class pixel_writer<pixel_byte>; + template class pixel_reader<pixel_byte>; + template class pixel_writer<uint16_t, pixel_byte>; + template class pixel_reader<uint16_t, pixel_byte>; + template class pixel_writer<rgb_pixel, pixel_byte>; + template class pixel_reader<rgb_pixel, pixel_byte>; + template class pixel_writer<rgba_pixel, pixel_byte>; + template class pixel_reader<rgba_pixel, pixel_byte>; + +// TODO: as if the stuff above wasn't enough, this can explode quadratically +// maybe just force user to instantiate themselves if they are working with obscure types that happen to fit here +// also are all these combinations useful?? +#define SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(Pixel,Vector,Param) \ + template void pixel_writer<Pixel,pixel_byte>::set<Vector>(const Param &, float2) const; +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgb_pixel, rgb_vector, rgb_vector) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgb_pixel, rgba_vector, rgba_vector) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgb_pixel, rgb_vector, rgb_pixel) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgb_pixel, rgba_vector, rgb_pixel) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgba_pixel, rgb_vector, rgb_vector) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgba_pixel, rgba_vector, rgba_vector) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgba_pixel, rgb_vector, rgba_pixel) +SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET(rgba_pixel, rgba_vector, rgba_pixel) +#undef SIMPLE_SUPPORT_GRAPHICAL_PIXEL_WRITER_SET + +} // namespace simple::graphical diff --git a/source/simple/graphical/pixels.h b/source/simple/graphical/pixels.h new file mode 100644 index 0000000000000000000000000000000000000000..d49ad1d819eee59733ce41273a6aa75818f4b8b8 --- /dev/null +++ b/source/simple/graphical/pixels.h @@ -0,0 +1,172 @@ +#ifndef SIMPLE_GRAPHICAL_PIXELS_H +#define SIMPLE_GRAPHICAL_PIXELS_H + +#include <variant> +#include <cassert> + +#include "common_def.h" +#include "color_vector.hpp" + +namespace simple::graphical +{ + + class color; + + template<typename RawType> + class pixels + { + // TODO: SDL independent pixels owner, compatible with the views below + }; + + namespace pixel_view_details + { + + class tag + { + public: + class pixel_view {}; + class reader : public pixel_view {}; + class writer : public pixel_view {}; + + template <typename Tag> + static constexpr bool valid = std::is_base_of_v<pixel_view, Tag>; + + + template<typename T, typename RT> + using select_raw_type = std::conditional_t<std::is_same_v<T, tag::reader>, std::add_const_t<RT>, RT>; + + }; + + template <typename Tag, typename Pixel, typename RawType> + static constexpr bool valid_impl = tag::valid<Tag> && (sizeof(Pixel) % sizeof(RawType) == 0); + + // template<typename Tag, typename Pixel, typename RawType, + // std::enable_if_t<valid_impl<Tag, Pixel, RawType>>* = nullptr> + template<typename Tag, typename Pixel, typename RawType> + class impl + { + + protected: + constexpr static int2 ratio{(int)(sizeof(Pixel) / sizeof(RawType)),1}; + + public: + using raw_type = tag::select_raw_type<Tag, RawType>; + using byte_type = tag::select_raw_type<Tag, pixel_byte>; + using pixel_type = Pixel; + + const int2& raw_size() const noexcept; + int2 size() const noexcept; + + // TODO: proper row_iterator interface? + raw_type* row(int index = 0) const noexcept; + + raw_type* row_offset(raw_type* row, int offset = 1) const noexcept; + + raw_type* next_row(raw_type* row) const noexcept; + + raw_type* prev_row(raw_type* row) const noexcept; + + raw_type& operator[](int2 position) const noexcept; + + simple::support::range<raw_type*> raw_range() const noexcept; + + auto get(int2 position) const + -> std::conditional_t<std::is_same_v<Pixel,RawType>, const Pixel&, Pixel>; + + impl(const impl & other, range2D range); + + int pitch() const noexcept; + + protected: + + impl(tag::select_raw_type<Tag,pixel_byte>* target, int2 size, int pitch = 0); + + template<typename OtherTag> + impl(const impl<OtherTag, Pixel, RawType> & other); + + template<typename OtherTag> + impl(const impl<OtherTag, Pixel, RawType> & other, range2D range); + + private: + tag::select_raw_type<Tag,pixel_byte>* _raw; + int2 _raw_size; + int _pitch; + + static int to_index(const int2& position, int pitch) noexcept; + + }; + + } // namespace pixel_view_details + + + // TODO: add more pixel types, for all the crazy pixel formats SDL supports + template<template<typename Pixel, typename Raw = Pixel> typename PixelView> + using pixel_view_variant = std::variant< + PixelView<pixel_byte>, // 1 bytes per pixel + PixelView<uint16_t, pixel_byte>, // 2 bytes per pixel + PixelView<rgb_pixel, pixel_byte>, // 3 bytes per pixel + PixelView<rgba_pixel, pixel_byte> // 4 bytes per pixel + >; + + template<typename Pixel, typename RawType = Pixel> + class pixel_writer : + public pixel_view_details::impl<pixel_view_details::tag::writer, Pixel, RawType> + { + public: + using impl = pixel_view_details::impl<pixel_view_details::tag::writer, Pixel, RawType>; + using impl::impl; + + void set(const Pixel& pixel, int2 position) const; + + template <typename ColorVector = rgba_vector> + void set(const Pixel& pixel, float2 position) const; + + template <typename ColorVector = rgba_vector> + void set(const ColorVector& pixel, float2 position) const; + + void set(const color& pixel, int2 position) const; + + private: + pixel_writer(pixel_byte* target, int2 size, int pitch = 0); + friend pixel_view_variant<pixel_writer> pixel_writer_from_format(pixel_byte*, int2, int, int bpp); + }; + + template<typename Pixel, typename RawType> + pixel_writer(pixel_writer<Pixel, RawType>, range2D) + -> pixel_writer<Pixel, RawType>; + + template<typename Pixel, typename RawType = Pixel> + class pixel_reader : + public pixel_view_details::impl<pixel_view_details::tag::reader, Pixel, RawType> + { + public: + using impl = pixel_view_details::impl<pixel_view_details::tag::reader, Pixel, RawType>; + using impl::impl; + + pixel_reader(const pixel_writer<Pixel, RawType> & other); + + pixel_reader + ( + const pixel_writer<Pixel, RawType> & other, + range2D range + ); + }; + + template<typename Pixel, typename RawType> + pixel_reader(pixel_writer<Pixel, RawType>) + -> pixel_reader<Pixel, RawType>; + + template<typename Pixel, typename RawType> + pixel_reader(pixel_writer<Pixel, RawType>, range2D) + -> pixel_reader<Pixel, RawType>; + + template<typename Pixel, typename RawType> + pixel_reader(pixel_reader<Pixel, RawType>, range2D) + -> pixel_reader<Pixel, RawType>; + + using pixel_reader_variant = pixel_view_variant<pixel_reader>; + using pixel_writer_variant = pixel_view_variant<pixel_writer>; + +} // namespace simple::graphical + +#endif /* end of include guard */ diff --git a/source/simple/graphical/pixels.hpp b/source/simple/graphical/pixels.hpp index c6ebcf8aaf20354b553a6825ffff06219dd0e46a..d09614fd0fc44a045f68a1817e3447bb10447f45 100644 --- a/source/simple/graphical/pixels.hpp +++ b/source/simple/graphical/pixels.hpp @@ -1,243 +1,211 @@ -#ifndef SIMPLE_GRAPHICAL_PIXELS_H -#define SIMPLE_GRAPHICAL_PIXELS_H +#ifndef SIMPLE_GRAPHICAL_PIXELS_HPP +#define SIMPLE_GRAPHICAL_PIXELS_HPP +#include "pixels.h" #include <memory> -#include <variant> #include <cassert> #include "common_def.h" #include "color_vector.hpp" #include "simple/support/algorithm.hpp" +#include "color.h" namespace simple::graphical { - template<typename RawType> - class pixels - { - // TODO: SDL independent pixels owner, compatible with the views below - }; - namespace pixel_view_details { - class tag + template<typename T, typename P, typename R> + const int2& impl<T,P,R>::raw_size() const noexcept + { return _raw_size; } + template<typename T, typename P, typename R> + int2 impl<T,P,R>::size() const noexcept + { return _raw_size / ratio; } + + template<typename T, typename P, typename R> + auto impl<T,P,R>::row(int index) const noexcept + -> raw_type* + { return reinterpret_cast<raw_type*>(_raw + index * _pitch); } + + template<typename T, typename P, typename R> + auto impl<T,P,R>::row_offset(raw_type* row, int offset) const noexcept + -> raw_type* + { return reinterpret_cast<raw_type*>( + reinterpret_cast<byte_type*>(row) + + offset * _pitch + ); } + + template<typename T, typename P, typename R> + auto impl<T,P,R>::next_row(raw_type* row) const noexcept + -> raw_type* + { return reinterpret_cast<raw_type*>( + reinterpret_cast<byte_type*>(row) + _pitch + ); } + + template<typename T, typename P, typename R> + auto impl<T,P,R>::prev_row(raw_type* row) const noexcept + -> raw_type* + { return reinterpret_cast<raw_type*>( + reinterpret_cast<byte_type*>(row) - _pitch + ); } + + template<typename T, typename P, typename R> + auto impl<T,P,R>::operator[](int2 position) const noexcept + -> raw_type& { - public: - class pixel_view {}; - class reader : public pixel_view {}; - class writer : public pixel_view {}; - - template <typename Tag> - static constexpr bool valid = std::is_base_of_v<pixel_view, Tag>; + assert(int2::zero() <= position && position < raw_size()); + return row(position.y())[position.x()]; + } - template<typename T, typename RT> - using select_raw_type = std::conditional_t<std::is_same_v<T, tag::reader>, std::add_const_t<RT>, RT>; - - }; - - template<typename Tag, typename Pixel, typename RawType, - std::enable_if_t<sizeof(Pixel) % sizeof(RawType) == 0>* = nullptr, - std::enable_if_t<tag::valid<Tag>>* = nullptr> - class impl + template<typename T, typename P, typename R> + auto impl<T,P,R>::raw_range() const noexcept + -> simple::support::range<raw_type*> { + return {row(), row_offset(row(),raw_size().y())}; + } - constexpr static int ratio = sizeof(Pixel) / sizeof(RawType); - - public: - explicit impl(const pixels<RawType>&); - using pixel_type = Pixel; - - const int2& raw_size() const noexcept { return _raw_size; } - int2 size() const noexcept { return _raw_size / int2(ratio, 1); } - - // TODO: proper row_iterator interface? - template<typename T = Tag, typename RT = tag::select_raw_type<T, RawType>> - RT* row(int index = 0) const noexcept - { return reinterpret_cast<RT*>(_raw + index * _pitch); } - - template<typename T = Tag, typename RT = tag::select_raw_type<T, RawType>> - RT* row_offset(RawType* row, int offset = 1) const noexcept - { return reinterpret_cast<RT*>(reinterpret_cast<pixel_byte*>(row) + offset * _pitch); } - - template<typename T = Tag, typename RT = tag::select_raw_type<T, RawType>> - RT* next_row(RT* row) const noexcept - { return reinterpret_cast<RT*>(reinterpret_cast<pixel_byte*>(row) + _pitch); } - - template<typename T = Tag, typename RT = tag::select_raw_type<T, RawType>> - RT* prev_row(RawType* row) const noexcept - { return reinterpret_cast<RT*>(reinterpret_cast<pixel_byte*>(row) - _pitch); } - - RawType& operator[](int2 position) const noexcept - { return row(position.y())[position.x()]; } - - auto get(int2 position) const - -> std::conditional_t<std::is_same_v<Pixel,RawType>, const Pixel&, Pixel> - { - assert(int2::zero() <= position && position < size()); - if constexpr(std::is_same_v<Pixel,RawType>) - return (*this)[position]; - else - { - Pixel pixel; - position.x() *= ratio; - memcpy(&pixel, &(*this)[position], sizeof(Pixel)); - return pixel; - } - } - - template<typename T=Tag, typename UInt, - std::enable_if_t<std::is_same_v<T, tag::writer> && std::is_unsigned_v<UInt>>* = nullptr> - void set(const UInt& pixel, int2 position) const - { - assert(int2::zero() <= position && position < size()); - position.x() *= ratio; - memcpy(&(*this)[position], &(pixel), sizeof(Pixel)); - } - - template<typename T=Tag, std::enable_if_t<std::is_same_v<T, tag::writer>>* = nullptr> - void set(const Pixel& pixel, int2 position) const - { - assert(int2::zero() <= position && position < size()); - if constexpr(std::is_same_v<Pixel,RawType>) - (*this)[position] = pixel; - else - { - position.x() *= ratio; - memcpy(&(*this)[position], &(pixel), sizeof(Pixel)); - } - } - - template <typename ColorVector = rgba_vector, - typename T=Tag, std::enable_if_t<std::is_same_v<T, tag::writer>>* = nullptr> - void set(const Pixel& pixel, float2 position) const - { - set(static_cast<ColorVector>(pixel), position); - } - - template <typename ColorVector = rgba_vector, - typename T=Tag, std::enable_if_t<std::is_same_v<T, tag::writer>>* = nullptr> - void set(const ColorVector& pixel, float2 position) const + template<typename T, typename Pixel, typename RawType> + auto impl<T,Pixel,RawType>::get(int2 position) const + -> std::conditional_t<std::is_same_v<Pixel,RawType>, const Pixel&, Pixel> + { + assert(int2::zero() <= position && position < size()); + if constexpr(std::is_same_v<Pixel,RawType>) + return (*this)[position]; + else { - auto floor = int2(position); - auto fraction = position - float2(floor); - - auto i = float2::zero(); - - // 2D specific bound checking TODO: try to move this out if possible, make algorithm N dimensional - auto begin = (floor[0] != size()[0] - 1) ? i.begin() : i.begin() + 1; - auto end = (floor[1] != size()[1] - 1) ? i.end() : i.end() - 1; - - do - { - // the actual magic - auto ratio = (float2::one() - fraction)*(float2::one() - i) + fraction * i; - auto opacity = std::accumulate(ratio.begin(), ratio.end(), 1.f, std::multiplies{}); - - // alpha blending TODO: move out - if constexpr (ColorVector::dimensions >= 4) - opacity *= pixel.a(); - ColorVector old_color {get(floor + int2(i))}; - set(Pixel(pixel * opacity + old_color * (1 - opacity)), floor + int2(i)); - } - while(simple::support::next_number(begin, end) != end); + Pixel pixel; + position.x() *= ratio.x(); + memcpy(&pixel, &(*this)[position], sizeof(Pixel)); + return pixel; } + } + + template<typename T, typename P, typename R> + impl<T,P,R>::impl(const impl & other, range2D range) : + _raw(reinterpret_cast<tag::select_raw_type<T,pixel_byte>*>(other.row()) + + to_index(range.lower(), other.pitch())), + _raw_size((range.upper() - range.lower())*ratio), + _pitch(other.pitch()) + {} - explicit impl(const impl & other) - : _raw(other._raw), _raw_size(other._raw_size), _pitch(other._pitch) - {} - - explicit impl(const impl & other, range2D range) : - _raw(other._raw + to_index(range.lower(), other._pitch)), - _raw_size(range.upper() - range.lower()), - _pitch(other._pitch) - {} - - protected: - - impl(pixel_byte* target, int2 size, int pitch = 0) - : _raw(target), _raw_size(size), _pitch(pitch ? pitch : size.x() * sizeof(RawType)) - {} - - int pitch() const noexcept { return _pitch; } - - template<typename OtherTag> - explicit impl(const impl<OtherTag, Pixel, RawType> & other) - : _raw(other._raw), _raw_size(other._raw_size), _pitch(other._pitch) - {} + template<typename T, typename P, typename R> + int impl<T,P,R>::pitch() const noexcept { return _pitch; } - template<typename OtherTag> - explicit impl(const impl<OtherTag, Pixel, RawType> & other, range2D range) : - _raw(other._raw + to_index(range.lower(), other._pitch)), - _raw_size(range.upper() - range.lower()), - _pitch(other._pitch) - {} + template<typename T, typename P, typename RawType> + impl<T,P,RawType>::impl(tag::select_raw_type<T,pixel_byte>* target, int2 size, int pitch) + : _raw(target), _raw_size(size), _pitch(pitch ? pitch : size.x() * sizeof(RawType)) + {} - private: - pixel_byte* _raw; - int2 _raw_size; - int _pitch; + template<typename T, typename P, typename R> + template<typename OtherTag> + impl<T,P,R>::impl(const impl<OtherTag, P, R> & other) : + _raw(reinterpret_cast<tag::select_raw_type<T,pixel_byte>*>(other.row())), + _raw_size(other.raw_size()), + _pitch(other.pitch()) + {} - static int to_index(const int2& position, int pitch) noexcept - { - return position.x() * sizeof(RawType) + position.y() * pitch; - } + template<typename T, typename P, typename R> + template<typename OtherTag> + impl<T,P,R>::impl(const impl<OtherTag, P, R> & other, range2D range) : + _raw(reinterpret_cast<tag::select_raw_type<T,pixel_byte>*>(other.row()) + + to_index(range.lower(), other.pitch())), + _raw_size((range.upper() - range.lower())*ratio), + _pitch(other.pitch()) + {} - }; + template<typename T, typename P, typename RawType> + int impl<T,P,RawType>::to_index(const int2& position, int pitch) noexcept + { + return position.x() * ratio.x() * sizeof(RawType) + position.y() * pitch; + } } // namespace pixel_view_details - // TODO: add more pixel types, for all the crazy pixel formats SDL supports - template<template<typename Pixel, typename Raw = Pixel> typename PixelView> - using pixel_view_variant = std::variant< - PixelView<pixel_byte>, // 1 bytes per pixel - PixelView<uint16_t, pixel_byte>, // 2 bytes per pixel - PixelView<rgb_pixel, pixel_byte>, // 3 bytes per pixel - PixelView<rgba_pixel, pixel_byte> // 4 bytes per pixel - >; - - class pixel_format; + template <typename P, typename R> + pixel_writer<P,R>::pixel_writer(pixel_byte* target, int2 size, int pitch) + : impl(target, size, pitch) + {} - template<typename Pixel, typename RawType = Pixel> - class pixel_writer : - public pixel_view_details::impl<pixel_view_details::tag::writer, Pixel, RawType> + template<typename Pixel, typename RawType> + void pixel_writer<Pixel,RawType>::set(const Pixel& pixel, int2 position) const { - public: - using impl = pixel_view_details::impl<pixel_view_details::tag::writer, Pixel, RawType>; - using impl::impl; - - private: - pixel_writer(pixel_byte* target, int2 size, int pitch = 0) - : impl(target, size, pitch) - {} - friend pixel_view_variant<pixel_writer> pixel_writer_from_format(pixel_byte*, int2, int, int bpp); - }; + assert(int2::zero() <= position && position < this->size()); + if constexpr(std::is_same_v<Pixel,RawType>) + (*this)[position] = pixel; + else + { + position.x() *= pixel_writer::ratio.x(); + memcpy(&(*this)[position], &(pixel), sizeof(Pixel)); + } + } + + template<typename Pixel, typename R> + template <typename ColorVector> + void pixel_writer<Pixel,R>::set(const Pixel& pixel, float2 position) const + { + set(static_cast<ColorVector>(pixel), position); + } - template<typename Pixel, typename RawType = Pixel> - class pixel_reader : - public pixel_view_details::impl<pixel_view_details::tag::reader, Pixel, RawType> + template<typename Pixel, typename R> + template <typename ColorVector> + void pixel_writer<Pixel,R>::set(const ColorVector& pixel, float2 position) const { - public: - using impl = pixel_view_details::impl<pixel_view_details::tag::reader, Pixel, RawType>; - using impl::impl; + auto floor = int2(position); + auto fraction = position - float2(floor); - explicit pixel_reader - (const pixel_writer<Pixel, RawType> & other) - : impl(other) - {} + auto i = float2::zero(); - explicit pixel_reader - ( - const pixel_writer<Pixel, RawType> & other, - range2D range - ) - : impl(other, range) - {} - }; + // 2D specific bound checking TODO: try to move this out if possible, make algorithm N dimensional + auto begin = (floor[0] != this->size()[0] - 1) ? i.begin() : i.begin() + 1; + auto end = (floor[1] != this->size()[1] - 1) ? i.end() : i.end() - 1; + + do + { + // the actual magic + auto ratio = (float2::one() - fraction)*(float2::one() - i) + fraction * i; + auto opacity = std::accumulate(ratio.begin(), ratio.end(), 1.f, std::multiplies{}); + + // alpha blending TODO: move out + if constexpr (ColorVector::dimensions >= 4) + opacity *= pixel.a(); + assert(opacity >= 0.f && opacity <= 1.f); + ColorVector old_color {this->get(floor + int2(i))}; + // TODO: consider skipping setting pixels with opacity 0(or near 0) + // can help performace in cases where the pixel lies on a single row + // line drawing, scan conversion + // need to benchmark + // this can be done at a lower level of set or in a "blender", by checking if what we set is equivalent to what we got, once alpha blending is moved out + set(Pixel(pixel * opacity + old_color * (1 - opacity)), floor + int2(i)); + } + while(simple::support::next_number(begin, end) != end); + } + + template<typename Pixel, typename R> + void pixel_writer<Pixel,R>::set(const color& pixel, int2 position) const + { + assert(int2::zero() <= position && position < this->size()); + static_assert( sizeof(color) >= sizeof(Pixel), "color should fill a pixel, at least" ); + position.x() *= pixel_writer::ratio.x(); + memcpy(&(*this)[position], &(pixel), sizeof(Pixel)); + } + + template <typename Pixel, typename RawType> + pixel_reader<Pixel,RawType>::pixel_reader + (const pixel_writer<Pixel, RawType> & other) + : impl(other) + {} + + template <typename Pixel, typename RawType> + pixel_reader<Pixel,RawType>::pixel_reader + ( + const pixel_writer<Pixel, RawType> & other, + range2D range + ) + : impl(other, range) + {} - using pixel_reader_variant = pixel_view_variant<pixel_reader>; - using pixel_writer_variant = pixel_view_variant<pixel_writer>; } // namespace simple::graphical diff --git a/source/simple/graphical/renderer.h b/source/simple/graphical/renderer.h index 17762e4eddc38e23ae2bf30b3583d49b19f5aba3..15094c0ec5083021c2135fa529cc28619f048cc0 100644 --- a/source/simple/graphical/renderer.h +++ b/source/simple/graphical/renderer.h @@ -7,7 +7,7 @@ #include "common_def.h" #include "color.h" #include "texture.h" -#include "pixels.hpp" +#include "pixels.h" namespace simple::graphical { diff --git a/source/simple/graphical/surface.h b/source/simple/graphical/surface.h index f7ff1a56eeac9d38edac5e11050eb468b13d1f47..3e28bbeeb5e04e96e54bcaaf182ad89f06989c70 100644 --- a/source/simple/graphical/surface.h +++ b/source/simple/graphical/surface.h @@ -8,7 +8,7 @@ #include "common_def.h" #include "pixel_format.h" -#include "pixels.hpp" +#include "pixels.h" #include "color.h" namespace simple::graphical