#include #include #include #include #include #include #include #include namespace LibImage { BAN::ErrorOr> Image::load_from_file(BAN::StringView path) { int fd = -1; if (path.data()[path.size()] == '\0') { fd = open(path.data(), O_RDONLY); } else { BAN::String path_str; TRY(path_str.append(path)); fd = open(path_str.data(), O_RDONLY); } if (fd == -1) { fprintf(stddbg, "open: %s\n", strerror(errno)); return BAN::Error::from_errno(errno); } BAN::ScopeGuard guard_file_close([fd] { close(fd); }); struct stat st; if (fstat(fd, &st) == -1) { fprintf(stddbg, "fstat: %s\n", strerror(errno)); return BAN::Error::from_errno(errno); } void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { fprintf(stddbg, "mmap: %s\n", strerror(errno)); return BAN::Error::from_errno(errno); } BAN::ScopeGuard guard_munmap([&] { munmap(addr, st.st_size); }); auto image_data_span = BAN::ConstByteSpan(reinterpret_cast(addr), st.st_size); if (probe_netbpm(image_data_span)) return TRY(load_netbpm(image_data_span)); if (probe_png(image_data_span)) return TRY(load_png(image_data_span)); fprintf(stderr, "unrecognized image format\n"); return BAN::Error::from_errno(ENOTSUP); } struct FloatingColor { __m128 vals; FloatingColor() {} FloatingColor(float b, float g, float r, float a) : vals { b, g, r, a } {} FloatingColor(Image::Color c) : FloatingColor(c.b, c.g, c.r, c.a) {} FloatingColor operator*(float value) const { FloatingColor color; color.vals = _mm_mul_ps(vals, _mm_set1_ps(value)); return color; } FloatingColor operator+(FloatingColor other) const { FloatingColor color; color.vals = _mm_add_ps(this->vals, other.vals); return color; } Image::Color as_color() const { __m128i int32 = _mm_cvttps_epi32(this->vals); __m128i int16 = _mm_packs_epi32(int32, _mm_setzero_si128()); __m128i int8 = _mm_packus_epi16(int16, _mm_setzero_si128()); const uint32_t temp = _mm_cvtsi128_si32(int8); return Image::Color { .b = reinterpret_cast(&temp)[0], .g = reinterpret_cast(&temp)[1], .r = reinterpret_cast(&temp)[2], .a = reinterpret_cast(&temp)[3], }; } }; 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 float ratio_x = static_cast(width()) / new_width; const float ratio_y = static_cast(height()) / new_height; const auto get_clamped_color = [this](int64_t x, int64_t y) { x = BAN::Math::clamp(x, 0, width() - 1); y = BAN::Math::clamp(y, 0, height() - 1); return get_color(x, y); }; 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++) nearest_bitmap[y * new_width + x] = get_clamped_color(x * ratio_x, y * ratio_y); return TRY(BAN::UniqPtr::create(new_width, new_height, BAN::move(nearest_bitmap))); } case ResizeAlgorithm::Linear: { BAN::Vector bilinear_bitmap; TRY(bilinear_bitmap.resize(new_width * new_height)); const uint64_t temp_w = width() + 1; const uint64_t temp_h = height() + 1; BAN::Vector floating_bitmap; TRY(floating_bitmap.resize(temp_w * temp_h)); for (uint64_t y = 0; y < temp_h; y++) for (uint64_t x = 0; x < temp_w; x++) floating_bitmap[y * temp_w + x] = get_clamped_color(x, y); for (uint64_t y = 0; y < new_height; y++) { for (uint64_t x = 0; x < new_width; x++) { const float src_x = x * ratio_x; const float src_y = y * ratio_y; const float weight_x = BAN::Math::fmod(src_x, 1.0f); const float weight_y = BAN::Math::fmod(src_y, 1.0f); const uint64_t src_x_u64 = BAN::Math::clamp(src_x, 0, width() - 1); const uint64_t src_y_u64 = BAN::Math::clamp(src_y, 0, height() - 1); const auto tl = floating_bitmap[(src_y_u64 + 0) * temp_w + (src_x_u64 + 0)]; const auto tr = floating_bitmap[(src_y_u64 + 0) * temp_w + (src_x_u64 + 1)]; const auto bl = floating_bitmap[(src_y_u64 + 1) * temp_w + (src_x_u64 + 0)]; const auto br = floating_bitmap[(src_y_u64 + 1) * temp_w + (src_x_u64 + 1)]; const auto avg_t = tl * (1.0f - weight_x) + tr * weight_x; const auto avg_b = bl * (1.0f - weight_x) + br * weight_x; const auto avg = avg_t * (1.0f - weight_y) + avg_b * weight_y; bilinear_bitmap[y * new_width + x] = avg.as_color(); } } return TRY(BAN::UniqPtr::create(new_width, new_height, BAN::move(bilinear_bitmap))); } case ResizeAlgorithm::Cubic: { BAN::Vector bicubic_bitmap; TRY(bicubic_bitmap.resize(new_width * new_height, {})); constexpr auto cubic_interpolate = [](const FloatingColor p[4], float weight) -> FloatingColor { const auto a = (p[0] * -0.5) + (p[1] * 1.5) + (p[2] * -1.5) + (p[3] * 0.5); const auto b = p[0] + (p[1] * -2.5) + (p[2] * 2.0) + (p[3] * -0.5); const auto c = (p[0] * -0.5) + (p[2] * 0.5); const auto d = p[1]; return ((a * weight + b) * weight + c) * weight + d; }; const uint64_t temp_w = width() + 3; const uint64_t temp_h = height() + 3; BAN::Vector floating_bitmap; TRY(floating_bitmap.resize(temp_w * temp_h, {})); for (uint64_t y = 0; y < temp_h; y++) for (uint64_t x = 0; x < temp_w; x++) floating_bitmap[y * temp_w + x] = get_clamped_color( static_cast(x) - 1, static_cast(y) - 1 ); for (uint64_t y = 0; y < new_height; y++) { for (uint64_t x = 0; x < new_width; x++) { const float src_x = x * ratio_x; const float src_y = y * ratio_y; const float weight_x = BAN::Math::fmod(src_x, 1.0f); const float weight_y = BAN::Math::fmod(src_y, 1.0f); const uint64_t src_x_u64 = BAN::Math::clamp(src_x, 0, width() - 1) + 1; const uint64_t src_y_u64 = BAN::Math::clamp(src_y, 0, height() - 1) + 1; FloatingColor values[4]; for (int64_t m = -1; m <= 2; m++) { const FloatingColor p[4] { floating_bitmap[(src_y_u64 + m) * temp_w + (src_x_u64 - 1)], floating_bitmap[(src_y_u64 + m) * temp_w + (src_x_u64 + 0)], floating_bitmap[(src_y_u64 + m) * temp_w + (src_x_u64 + 1)], floating_bitmap[(src_y_u64 + m) * temp_w + (src_x_u64 + 2)], }; values[m + 1] = cubic_interpolate(p, weight_x); } bicubic_bitmap[y * new_width + x] = cubic_interpolate(values, weight_y).as_color(); } } return TRY(BAN::UniqPtr::create(new_width, new_height, BAN::move(bicubic_bitmap))); } } return BAN::Error::from_errno(EINVAL); } }