diff --git a/LibImage/Image.cpp b/LibImage/Image.cpp index 36954ce1..fb933ac0 100644 --- a/LibImage/Image.cpp +++ b/LibImage/Image.cpp @@ -76,4 +76,64 @@ namespace LibImage return BAN::Error::from_errno(ENOTSUP); } + BAN::ErrorOr> Image::resize(uint64_t new_width, uint64_t new_height, ResizeAlgorithm algorithm) + { + if (!validate_size(new_width, new_height)) + return BAN::Error::from_errno(EOVERFLOW); + + const double ratio_x = (double)width() / new_width; + const double ratio_y = (double)height() / new_height; + + switch (algorithm) + { + case ResizeAlgorithm::Nearest: + { + BAN::Vector nearest_bitmap; + TRY(nearest_bitmap.resize(new_width * new_height)); + + for (uint64_t y = 0; y < new_height; y++) + { + for (uint64_t x = 0; x < new_width; x++) + { + const uint64_t nearest_x = BAN::Math::clamp(x * ratio_x, 0, width() - 1); + const uint64_t nearest_y = BAN::Math::clamp(y * ratio_y, 0, height() - 1); + nearest_bitmap[y * new_width + x] = get_color(nearest_x, nearest_y); + } + } + + return TRY(BAN::UniqPtr::create(new_width, new_height, BAN::move(nearest_bitmap))); + } + case ResizeAlgorithm::Bilinear: + { + BAN::Vector bilinear_bitmap; + TRY(bilinear_bitmap.resize(new_width * new_height)); + + for (uint64_t y = 0; y < new_height; y++) + { + for (uint64_t x = 0; x < new_width; x++) + { + const double src_x_float = x * ratio_x; + const double src_y_float = y * ratio_y; + const double weight_x = src_x_float - floor(src_x_float); + const double weight_y = src_y_float - floor(src_y_float); + + const uint64_t src_l = BAN::Math::clamp(src_x_float, 0, width() - 1); + const uint64_t src_t = BAN::Math::clamp(src_y_float, 0, height() - 1); + + const uint64_t src_r = BAN::Math::clamp(src_l + 1, 0, width() - 1); + const uint64_t src_b = BAN::Math::clamp(src_t + 1, 0, height() - 1); + + const Color avg_t = Color::average(get_color(src_l, src_t), get_color(src_r, src_t), weight_x); + const Color avg_b = Color::average(get_color(src_l, src_b), get_color(src_r, src_b), weight_x); + bilinear_bitmap[y * new_width + x] = Color::average(avg_t, avg_b, weight_y); + } + } + + return TRY(BAN::UniqPtr::create(new_width, new_height, BAN::move(bilinear_bitmap))); + } + } + + return BAN::Error::from_errno(EINVAL); + } + } diff --git a/LibImage/Netbpm.cpp b/LibImage/Netbpm.cpp index ea7bca3e..18c95f10 100644 --- a/LibImage/Netbpm.cpp +++ b/LibImage/Netbpm.cpp @@ -77,9 +77,9 @@ namespace LibImage image_data = image_data.slice(1); auto height = opt_height.value(); - if (BAN::Math::will_multiplication_overflow(width, height) || BAN::Math::will_multiplication_overflow(width * height, 3)) + if (!Image::validate_size(width, height)) { - fprintf(stddbg, "invalid Netbpm image (size is over 64 bits overflows)\n"); + fprintf(stddbg, "invalid Netbpm image size\n"); return BAN::Error::from_errno(EINVAL); } diff --git a/LibImage/include/LibImage/Image.h b/LibImage/include/LibImage/Image.h index 0e9c0775..503f0367 100644 --- a/LibImage/include/LibImage/Image.h +++ b/LibImage/include/LibImage/Image.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -16,24 +17,61 @@ namespace LibImage uint8_t g; uint8_t b; uint8_t a; + + // Calculate weighted average of colors + // weight of 0.0 returns a and weight of 1.0 returns b + static Color average(Color a, Color b, double weight) + { + const double b_mult = BAN::Math::clamp(weight, 0.0, 1.0); + const double a_mult = 1.0 - b_mult; + return Color { + .r = static_cast(a.r * a_mult + b.r * b_mult), + .g = static_cast(a.g * a_mult + b.g * b_mult), + .b = static_cast(a.b * a_mult + b.b * b_mult), + .a = static_cast(a.a * a_mult + b.a * b_mult), + }; + } + }; + + enum class ResizeAlgorithm + { + Nearest, + Bilinear, }; public: static BAN::ErrorOr> load_from_file(BAN::StringView path); + BAN::ErrorOr> resize(uint64_t new_width, uint64_t new_height, ResizeAlgorithm = ResizeAlgorithm::Bilinear); + Color get_color(uint64_t x, uint64_t y) const { return m_bitmap[y * width() + x]; } const BAN::Vector bitmap() const { return m_bitmap; } uint64_t width() const { return m_width; } uint64_t height() const { return m_height; } + static constexpr bool validate_size(uint64_t width, uint64_t height) + { + // width and height must fit in int64_t and width * height * sizeof(Color) has to not overflow + if (width > static_cast(BAN::numeric_limits::max())) + return false; + if (height > static_cast(BAN::numeric_limits::max())) + return false; + if (BAN::Math::will_multiplication_overflow(width, height)) + return false; + if (BAN::Math::will_multiplication_overflow(width * height, sizeof(Color))) + return false; + return true; + } + private: Image(uint64_t width, uint64_t height, BAN::Vector&& bitmap) : m_width(width) , m_height(height) , m_bitmap(BAN::move(bitmap)) { - ASSERT(m_bitmap.size() >= width * height); + ASSERT(validate_size(m_width, m_height)); + ASSERT(m_bitmap.size() >= m_width * m_height); } private: