diff --git a/userspace/programs/CMakeLists.txt b/userspace/programs/CMakeLists.txt index 6cfddf5f..4843f5df 100644 --- a/userspace/programs/CMakeLists.txt +++ b/userspace/programs/CMakeLists.txt @@ -19,6 +19,7 @@ set(USERSPACE_PROGRAMS http-server id image + ImageViewer init kill less diff --git a/userspace/programs/ImageViewer/CMakeLists.txt b/userspace/programs/ImageViewer/CMakeLists.txt new file mode 100644 index 00000000..b0294585 --- /dev/null +++ b/userspace/programs/ImageViewer/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES + main.cpp +) + +add_executable(ImageViewer ${SOURCES}) +banan_link_library(ImageViewer ban) +banan_link_library(ImageViewer libc) +banan_link_library(ImageViewer libgui) +banan_link_library(ImageViewer libimage) + +install(TARGETS ImageViewer OPTIONAL) diff --git a/userspace/programs/ImageViewer/main.cpp b/userspace/programs/ImageViewer/main.cpp new file mode 100644 index 00000000..506698d3 --- /dev/null +++ b/userspace/programs/ImageViewer/main.cpp @@ -0,0 +1,181 @@ +#include +#include +#include + +#include + +static constexpr size_t s_window_width = 400; +static constexpr size_t s_window_height = 400; +static constexpr float s_scale_factor = 0.9f; + +static void run(LibImage::Image& image, LibGUI::Window& window) +{ + int32_t xoff = static_cast(window.width() - image.width()) / 2; + int32_t yoff = static_cast(window.height() - image.height()) / 2; + float scale = 1.0f; + + bool mouse_pressed = false; + int32_t mouse_x = 0; + int32_t mouse_y = 0; + + bool should_close = false; + bool should_display = false; + bool linear = false; + + const auto display_image = + [&]() -> void + { + window.texture().fill(0xFFFFFF); + + for (int32_t winy = 0; winy < static_cast(window.height()); winy++) + { + for (int32_t winx = 0; winx < static_cast(window.width()); winx++) + { + LibImage::Image::Color color; + + if (!linear) + { + const int32_t imgx = BAN::Math::round((winx - xoff) / scale); + const int32_t imgy = BAN::Math::round((winy - yoff) / scale); + if (imgx < 0 || imgx >= static_cast(image.width())) + continue; + if (imgy < 0 || imgy >= static_cast(image.height())) + continue; + color = image.get_color(imgx, imgy); + } + else + { + const float fimgx = (winx - xoff) / scale; + const float fimgy = (winy - yoff) / scale; + if (fimgx + 0.5f < 0 || fimgx + 0.5f >= image.width()) + continue; + if (fimgy + 0.5f < 0 || fimgy + 0.5f >= image.height()) + continue; + + const int32_t imgl = BAN::Math::clamp(fimgx, 0, image.width() - 1); + const int32_t imgr = BAN::Math::clamp(imgl + 1, 0, image.width() - 1); + const int32_t imgt = BAN::Math::clamp(fimgy, 0, image.height() - 1); + const int32_t imgb = BAN::Math::clamp(imgt + 1, 0, image.height() - 1); + + const auto tl = image.get_color(imgl, imgt); + const auto tr = image.get_color(imgr, imgt); + const auto bl = image.get_color(imgl, imgb); + const auto br = image.get_color(imgr, imgb); + + const float weightx = fimgx - BAN::Math::floor(fimgx); + const float weighty = fimgy - BAN::Math::floor(fimgy); + + color = LibImage::Image::Color::average( + LibImage::Image::Color::average(tl, tr, weightx), + LibImage::Image::Color::average(bl, br, weightx), + weighty + ); + } + + window.texture().set_pixel(winx, winy, color.as_argb()); + } + } + + window.invalidate(); + }; + + window.set_close_window_event_callback([&] { + should_close = true; + }); + + window.set_resize_window_event_callback([&] { + should_display = true; + }); + + window.set_mouse_button_event_callback([&](auto event) { + if (event.button != LibInput::MouseButton::Left) + return; + mouse_pressed = event.pressed; + mouse_x = event.x; + mouse_y = event.y; + }); + + window.set_mouse_move_event_callback([&](auto event) { + if (mouse_pressed) + { + xoff += event.x - mouse_x; + yoff += event.y - mouse_y; + should_display = true; + } + mouse_x = event.x; + mouse_y = event.y; + }); + + window.set_mouse_scroll_event_callback([&](auto event) { + const float new_scale = scale * BAN::Math::pow(s_scale_factor, -event.scroll); + xoff = mouse_x - (mouse_x - xoff) / scale * new_scale; + yoff = mouse_y - (mouse_y - yoff) / scale * new_scale; + scale = new_scale; + should_display = true; + }); + + window.set_key_event_callback([&](LibGUI::EventPacket::KeyEvent::event_t event) { + if (!event.pressed()) + return; + switch (event.key) + { + case LibInput::Key::Space: + linear = !linear; + should_display = true; + break; + default: + break; + } + }); + + display_image(); + + auto attributes = window.get_attributes(); + attributes.shown = true; + window.set_attributes(attributes); + + while (!should_close) + { + window.wait_events(); + window.poll_events(); + + if (should_display) + display_image(); + should_display = false; + } +} + +int main(int argc, char** argv) +{ + if (argc != 2) + { + fprintf(stderr, "usage: %s image_file\n", argv[0]); + return 1; + } + + auto image_or_error = LibImage::Image::load_from_file(argv[1]); + if (image_or_error.is_error()) + { + fprintf(stderr, "Failed to load '%s': %s\n", argv[1], image_or_error.error().get_message()); + return 1; + } + + auto image = image_or_error.release_value(); + + auto window_attributes = LibGUI::Window::default_attributes; + window_attributes.resizable = true; + window_attributes.shown = false; + + auto window_or_error = LibGUI::Window::create(s_window_width, s_window_height, argv[1], window_attributes); + if (window_or_error.is_error()) + { + fprintf(stderr, "Failed to create an window: %s\n", window_or_error.error().get_message()); + return 1; + } + + auto window = window_or_error.release_value(); + + run(*image, *window); + + return 0; +}