#pragma once #include #include #include #include #include #include #if defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC system_header #elif defined(_MSC_VER) #pragma warning(push, 0) #endif #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_STATIC #include "stb_image.h" #define STBI_MSC_SECURE_CRT #define STB_IMAGE_WRITE_STATIC #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" #if defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #elif defined(_MSC_VER) #pragma warning(pop) #endif namespace ztu { template class image { public: using value_type = C; using size_type = std::make_signed_t; using difference_type = size_type; using reference = std::add_lvalue_reference_t; using const_reference = std::add_const_t; using pointer = value_type*; using const_pointer = const value_type*; using iterator = pointer; using const_iterator = const_pointer; public: [[nodiscard]] static std::error_code load(const char* filename, image& dst, bool flip = false); [[nodiscard]] static image create(size_type width, size_type height, const value_type& color); public: image() = default; image(std::unique_ptr&& data, size_type width, size_type height); image(const image&); image(image&&) noexcept; [[nodiscard]] image& operator=(const image&); [[nodiscard]] image& operator=(image&&) noexcept; [[nodiscard]] value_type operator()(double x, double y) const; [[nodiscard]] const_reference operator()(size_type x, size_type y) const; [[nodiscard]] reference operator()(size_type x, size_type y); [[nodiscard]] const_iterator operator[](size_type y) const; [[nodiscard]] iterator operator[](size_type y); [[nodiscard]] int save(const std::string& filename) const; [[nodiscard]] bool contains(size_type x, size_type y) const; [[nodiscard]] size_type width() const; [[nodiscard]] size_type height() const; [[nodiscard]] std::pair size() const; [[nodiscard]] size_type num_pixels() const; [[nodiscard]] const_iterator begin() const; [[nodiscard]] iterator begin(); [[nodiscard]] const_iterator end() const; [[nodiscard]] iterator end(); [[nodiscard]] const_iterator cbegin() const; [[nodiscard]] const_iterator cend() const; [[nodiscard]] const_pointer data() const; [[nodiscard]] pointer data(); private: std::unique_ptr m_data{ nullptr }; size_type m_width{ 0 }, m_height{ 0 }; }; template image::image(std::unique_ptr&& data, const size_type width, const size_type height) : m_data{ std::move(data) }, m_width{ width }, m_height{ height } { }; template image::image(const image& other) : m_data{ new C[other.m_width * other.m_height] }, m_width{ other.m_width }, m_height{ other.m_height } { std::copy_n(other.m_data.get(), other.m_width * other.m_height, this->m_data.get()); } template image::image(image&& other) noexcept : m_data{ std::move(other.m_data) }, m_width{ other.m_width }, m_height{ other.m_height } { other.m_width = 0; other.m_height = 0; } template image& image::operator=(const image& other) { if (this != &other) { const auto m_num_pixels = m_width * m_height; const auto o_num_pixels = other.m_width * other.m_height; if (o_num_pixels > m_num_pixels) { this->~image(); this->m_data = new C[o_num_pixels]; } std::copy_n(other.m_data, o_num_pixels, this->m_data); this->m_width = other.m_width; this->m_height = other.m_height; } return *this; } template image& image::operator=(image&& other) noexcept { if (this != &other) { this->~image(); this->m_data = std::move(other.m_data); this->m_width = other.m_width; this->m_height = other.m_height; other.m_width = 0; other.m_height = 0; } return *this; } template std::error_code image::load(const char* filename, image& dst, bool flip) { int width, height, channels; stbi_set_flip_vertically_on_load(flip); auto data = reinterpret_cast(stbi_load(filename, &width, &height, &channels, sizeof(C))); if (data == nullptr) { return std::make_error_code(std::errc::no_such_file_or_directory); } dst.m_data.reset(data); dst.m_width = static_cast(width); dst.m_height = static_cast(height); return {}; } template image image::create(const size_type width, const size_type height, const C& color) { const auto num_pixels = width * height; C* data = new C[num_pixels]; std::fill_n(data, num_pixels, color); return image(data, width, height); } template typename image::size_type image::width() const { return m_width; } template typename image::size_type image::height() const { return m_height; } template std::pair::size_type, typename image::size_type> image::size() const { return { m_width, m_height }; } template typename image::size_type image::num_pixels() const { return m_width * m_height; } template int image::save(const std::string& filename) const { std::string ext = filename.substr(filename.rfind('.') + 1, filename.length()); std::transform( ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); } ); int status = -1; if (ext == "png") { status = stbi_write_png(filename.c_str(), m_width, m_height, sizeof(C), m_data, m_width * sizeof(C)); } else if (ext == "bmp") { status = stbi_write_bmp(filename.c_str(), m_width, m_height, sizeof(C), m_data); } else if (ext == "tga") { status = stbi_write_tga(filename.c_str(), m_width, m_height, sizeof(C), m_data); } else if (ext == "jpg" || ext == "jpeg") { status = stbi_write_jpg(filename.c_str(), m_width, m_height, sizeof(C), m_data, m_width * sizeof(C)); } return status; } template bool image::contains(size_type x, size_type y) const { return 0 <= x and x < m_width and 0 <= y and y < m_height; } template typename image::const_reference image::operator()(size_type x, size_type y) const { const auto clamped_x = std::clamp(x, static_cast(0), m_width - 1); const auto clamped_y = std::clamp(y, static_cast(0), m_height - 1); return m_data[clamped_x + clamped_y * m_width]; } template typename image::reference image::operator()(size_type x, size_type y) { const auto clamped_x = std::clamp(x, static_cast(0), m_width - 1); const auto clamped_y = std::clamp(y, static_cast(0), m_height - 1); return m_data[clamped_x + clamped_y * m_width]; } template typename image::value_type image::operator()(double x, double y) const { auto min_x = static_cast(std::floor(x)); auto min_y = static_cast(std::floor(y)); const auto px00 = (*this)(min_x, min_y), px10 = (*this)(min_x + 1, min_y); const auto px01 = (*this)(min_x, min_y + 1), px11 = (*this)(min_x + 1, min_y + 1); const auto a_x = x - static_cast(min_x), a_y = y - static_cast(min_y); return std::lerp( std::lerp(px00, px10, a_x), std::lerp(px01, px11, a_x), a_y ); } template typename image::const_iterator image::operator[](size_type y) const { return &m_data[y * m_width]; } template typename image::iterator image::operator[](size_type y) { return &m_data[y * m_width]; } template typename image::const_iterator image::begin() const { return m_data.get(); } template typename image::iterator image::begin() { return m_data.get(); } template typename image::const_iterator image::end() const { return m_data.get() + m_width * m_height; } template typename image::iterator image::end() { return m_data.get() + m_width * m_height; } template typename image::const_iterator image::cbegin() const { return const_cast(begin()); } template typename image::const_iterator image::cend() const { return const_cast(begin()); } template typename image::const_pointer image::data() const { return m_data.get(); } template typename image::pointer image::data() { return m_data.get(); } } // namespace ztu