From b44fe94d5c6e8659730ee1afa2466b4720c06cf9 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Fri, 25 Sep 2026 22:33:30 -0700 Subject: [PATCH] image view example --- examples/image_viewer/CMakeLists.txt | 7 +++ examples/image_viewer/dog.jpeg | Bin 0 -> 8452 bytes examples/image_viewer/main.cpp | 79 +++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 examples/image_viewer/CMakeLists.txt create mode 100644 examples/image_viewer/dog.jpeg create mode 100644 examples/image_viewer/main.cpp diff --git a/examples/image_viewer/CMakeLists.txt b/examples/image_viewer/CMakeLists.txt new file mode 100644 index 0000000..8719d57 --- /dev/null +++ b/examples/image_viewer/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(image_viewer main.cpp) + +target_link_libraries(image_viewer PRIVATE ${PROJECT_NAME}) + +target_compile_definitions(image_viewer PRIVATE + IMAGE_VIEWER_ASSET_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) diff --git a/examples/image_viewer/dog.jpeg b/examples/image_viewer/dog.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..26e172588ac9f2e7e3aac4cf173abe20ce13ddb2 GIT binary patch literal 8452 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 literal 0 HcmV?d00001 diff --git a/examples/image_viewer/main.cpp b/examples/image_viewer/main.cpp new file mode 100644 index 0000000..2eae2df --- /dev/null +++ b/examples/image_viewer/main.cpp @@ -0,0 +1,79 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +// Shows a render target filling the whole panel. +class ViewportPanel : public sophia::Panel { + public: + ViewportPanel(sophia::UI& ui, sophia::RenderTargetHandle target) + : Panel{{.name = "Image", + .windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse}}, + ui{ui}, + target{target} {} + + void onUpdate(const sophia::FrameInfo&) override { ui.image(target); } + + private: + sophia::UI& ui; + sophia::RenderTargetHandle target; +}; + +// Displays an image file (anything stb_image reads: JPEG, PNG, BMP, ...) as a +// sprite in a 2D scene, scaled to fit the viewport. +// image_viewer [path] +class ImageViewer : public sophia::Application { + public: + ImageViewer() + : Application{{.width = 1000, .height = 750, .name = "ImageViewer"}} {} + + void onCreate() override { + std::string path = + args.size() > 1 ? args[1] : IMAGE_VIEWER_ASSET_DIR "/dog.jpeg"; + + sophia::SceneHandle sceneHandle = scenes().create(); + sophia::Scene& scene = scenes().get(sceneHandle); + + camera = scene.createEntity("Camera"); + camera.addComponent(); + + sophia::TextureHandle texture = resources().load(path); + imageSize = resources().getExtent(texture); + + // One world unit per image pixel; the camera zooms to fit. + sophia::Entity image = scene.createEntity("Image"); + image.addComponent( + sophia::SpriteComponent{.texture = texture, .size = imageSize}); + + target = resources().create(); + views().create({.scene = sceneHandle, + .target = target, + .camera = camera, + .clearColor = {0.1f, 0.1f, 0.15f, 1.f}}); + + panels().emplace(ui(), target); + // panels().setDefaultLayout({.center = {"Image"}}); + } + + void onUpdate(const sophia::FrameInfo&) override { + // Fit the whole image: show enough height that neither dimension of the + // image overflows the viewport. + glm::vec2 viewport = resources().getExtent(target); + float viewportAspect = viewport.x / viewport.y; + camera.getComponent().ortho.height = + std::max(imageSize.y, imageSize.x / viewportAspect); + } + + private: + sophia::Entity camera; + sophia::RenderTargetHandle target{}; + glm::vec2 imageSize{1.f}; +}; + +SOPHIA_MAIN(ImageViewer)