Closed Andersama closed 1 year ago
Everything I know of has been listed in the Wiki: https://github.com/ocornut/imgui/wiki/Useful-Extensions
I suppose imgInspect and ImGuiTexInspect might be good reference but are more complex things. Your thing is pretty much down to calling Image() or ImDrawList::AddImage() and managing a transform. Also see e.g. Demo->Examples->Custom Rendering->Canvas for an idea of how to pan.
imgInspect
github/CedricGuillemet/imgInspect
ImGuiTexInspect
github/andyborrell/imgui_tex_inspect (discussion, online demo)
Yeah it's not particularly complicated that's why I was crossing fingers I might've missed something. I suppose the weird part would be that if I render inside an Imgui window I'd want to make sure mouse drags don't move the window around but instead manipulate the transform for the render.
You can use InvisibleButton() to capture inputs, this is what the canvas demo does.
Ah...shoot, I didn't even think to click over to that tab, that pretty tackles it, except for the scaling. I'm glad you followed up.
I was just fiddling with window flags, the invisible button makes significantly more sense.
ImGuiWindowFlags_::ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_::ImGuiWindowFlags_AlwaysHorizontalScrollbar |
ImGuiWindowFlags_::ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_::ImGuiWindowFlags_NoScrollWithMouse
Not exactly great control wise yet, but here's a rough mockup for anyone else.
if (ImGui::Begin(imgui_id.c_str())) {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// Get the current ImGui cursor position
ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available
// guarantee a minimum canvas size
canvas_size.x = std::max(canvas_size.x, 256.0f);
canvas_size.y = std::max(canvas_size.y, 250.0f);
ImVec2 canvas_p1 = ImVec2{canvas_p0.x + canvas_size.x, canvas_p0.y + canvas_size.y};
ImGui::InvisibleButton("##canvas", canvas_size,
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight |
ImGuiButtonFlags_MouseButtonMiddle);
const bool canvas_hovered = ImGui::IsItemHovered(); // Hovered
const bool canvas_active = ImGui::IsItemActive(); // Held
// const ImVec2 canvas_p0 = ImGui::GetItemRectMin(); // alternatively we can get the rectangle like this
// const ImVec2 canvas_p1 = ImGui::GetItemRectMax();
// Draw border and background color
ImGuiIO& io = ImGui::GetIO();
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255));
draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255));
// TODO: shift or ctrl to slow zoom movement
// if (canvas_active) {
float zoom_rate = 0.1f;
float zoom_mouse = io.MouseWheel * zoom_rate; //-0.1f 0.0f 0.1f
float hzoom_mouse = zoom_mouse * 0.5f;
float zoom_delta = zoom_mouse *
open_images[i].transform.scale.x; // each step grows or shrinks image by 10%
ImVec2 old_scale = open_images[i].transform.scale;
// on screen (top left of image)
ImVec2 old_origin = {canvas_p0.x + open_images[i].transform.translate.x,
canvas_p0.y + open_images[i].transform.translate.y};
// on screen (bottom right of image)
ImVec2 old_p1 = {old_origin.x + (open_images[i].width * open_images[i].transform.scale.x),
old_origin.y + (open_images[i].height * open_images[i].transform.scale.y)};
// on screen (center of what we get to see), when adjusting scale this doesn't change!
ImVec2 old_and_new_canvas_center = {
canvas_p0.x + canvas_size.x * 0.5f, canvas_p0.y + canvas_size.y * 0.5f};
// in image coordinate offset of the center
ImVec2 image_center = {
old_and_new_canvas_center.x - old_origin.x, old_and_new_canvas_center.y - old_origin.y};
ImVec2 old_uv_image_center = {
image_center.x / (open_images[i].width * open_images[i].transform.scale.x),
image_center.y / (open_images[i].height * open_images[i].transform.scale.y)};
open_images[i].transform.scale.x += zoom_delta;
open_images[i].transform.scale.y += zoom_delta;
// 2.0f -> 2x zoom in
// 1.0f -> normal
// 0.5f -> 2x zoom out
// TODO: clamp based on image size, do we go pixel level?
open_images[i].transform.scale.x = std::clamp(open_images[i].transform.scale.x, 0.01f, 100.0f);
open_images[i].transform.scale.y = std::clamp(open_images[i].transform.scale.y, 0.01f, 100.0f);
// on screen new target center
ImVec2 new_image_center = {(open_images[i].width * open_images[i].transform.scale.x *
old_uv_image_center.x),
(open_images[i].height * open_images[i].transform.scale.y *
old_uv_image_center.y)};
// readjust to center
open_images[i].transform.translate.x -= new_image_center.x - image_center.x;
open_images[i].transform.translate.y -= new_image_center.y - image_center.y;
// 0 out second parameter if a context menu is open
if (canvas_active && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 1.0f)) {
open_images[i].transform.translate.x += ImGui::GetIO().MouseDelta.x;
open_images[i].transform.translate.y += ImGui::GetIO().MouseDelta.y;
}
const ImVec2 origin(canvas_p0.x + open_images[i].transform.translate.x,
canvas_p0.y + open_images[i].transform.translate.y); // Lock scrolled origin
// we need to control the rectangle we're going to draw and the uv coordinates
const ImVec2 image_p1 = {origin.x + (open_images[i].transform.scale.x * open_images[i].width),
origin.y + (open_images[i].transform.scale.x * open_images[i].height)};
const ImVec2 mouse_pos_in_canvas(imio.MousePos.x - origin.x, imio.MousePos.y - origin.y);
draw_list->PushClipRect(ImVec2{canvas_p0.x + 2.0f, canvas_p0.y + 2.0f},
ImVec2{canvas_p1.x - 2.0f, canvas_p1.y - 2.0f}, true);
// draw things
draw_list->AddImage(open_images[i].texture_id, origin, image_p1);
// draw things
draw_list->PopClipRect();
}
ImGui::End();
@ocornut would there be a quick way to render only part of some region? I patched up the example but
draw_list->AddImage(open_images[i].texture_id, origin, image_p1);
will draw beyond the bounds of the provided canvas rectangle if the image is large enough. I know I could do the math to handle the uv coordinates, but this seems way simpler.
Never mind, found I think what I need:
draw_list->PushClipRect(ImVec2{canvas_p0.x + 2.0f, canvas_p0.y + 2.0f},
ImVec2{canvas_p1.x - 2.0f, canvas_p1.y - 2.0f}, true);
draw_list->AddImage(open_images[i].texture_id, origin, image_p1);
draw_list->PopClipRect();
I've run into something strange, when using mouse inputs left clicks and scroll wheeling seem to be properly handled in the right window, but right clicks are falling through to the first window rendered?
Ignore me, I hadn't set up windows with their own interaction flags so the first window processed every single event.
Those are two questions unrelated to the original question and need to be asked in a new issue with the same amount of care and details as any new issues, otherwise 90% of the time we don’t have enough data to answer you correctly.
Read FAQ entry about drawing textures and using texture coordinates.
Ok, well for context then although I've got it solved now. I had forgotten to limit right-clicking conditions to the canvas that was being hovered, so while the initial right mouse down would appear to work because the number of times it was processed wouldn't matter the release would be processed by the first window rendered.
Version/Branch of Dear ImGui: Version: any Branch: any
Back-end/Renderer/Compiler/OS
Back-ends: any Compiler: any Operating System: any
My Issue/Question:
In the process of writing a gui to help someone label image files to train an AI. After going through the documentation again I'm pretty sure there isn't a image viewer widget on it's own. I'm fine with writing my own, but I hope I didn't miss something obvious to do the basics of what I'm after. The goal would be to like in many image editors have an area where some fixed content is displayed (an image) but can be moved and zoomed in and out on by mouse. For this use case the widget wouldn't be particularly complex so I figure it might be a good starting point for other editors.
Here are a few potential useful features: A shortcut / button to recenter the view A shortcut / button to set the zoom back to 100%
Screenshots/Video
Example code: