Open thewoz opened 4 months ago
A few problems I notice in your code:
mousePos.x
, you don't take the window position into account.mousePosInImage = (mousePos - windowPos + scroll) / scale
That can be resolved to this (after adjusting the scale)...
scroll = mousePosInImage * scale + windowPos - mousePos
Hi @GamingMinds-DanielC thank you very much for your reply. I understood what you say in (1) and (2) I made the changes and things are much better.
The code now looks like this:
ImVec2 mousePosInImage;
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y + ImGui::GetScrollY()) / imageScale;
// whether the wheel has been moved
if(ImGui::GetIO().MouseWheel != 0 && ImGui::GetIO().KeyCtrl) {
imageScale = imageScale * pow(1.50, ImGui::GetIO().MouseWheel);
if(imageScale < 0.1) imageScale = 0.1;
if(imageScale > 30) imageScale = 30;
// I update the scroll position
ImGui::SetScrollX(mousePosInImage.x * imageScale + ImGui::GetWindowPos().x - ImGui::GetIO().MousePos.x);
ImGui::SetScrollY(mousePosInImage.y * imageScale + ImGui::GetWindowPos().y - ImGui::GetIO().MousePos.y);
}
In general I have difficulty understanding the general structure of imgui coordinate system.
For point (3) I don't quite understand what you are saying. To whom do I not communicate the sliding value in advance?
Some more infos for point 3:
When you call ImGui::Begin("Left", ...)
, the scroll position will be clamped to the content size of the window, to avoid scrolling outside of the available content. That is intended, but the content size is known to the window as the content size of the last frame. If your content size is f.e. 1000 and your window is 600 high, the scroll position will be clamped to 1000-600=400.
If you zoom in on the bottom so that your new content height will be 1200 and calculate your scroll position to be 600, that will be the correct value. But the window will not know of the new content height, think it will probably be 1000 like in the last frame and clamp your scroll position to 400 again. Only when you submit your image will the new size be known, but then it is too late and the bottom 200 pixels will be scrolled out instead of being visible.
To avoid this, you can tell your window in advance. You can call ImGui::SetNextWindowContentSize()
followed by ImGui::SetNextWindowScroll()
, both before beginning the window, then the correct values will be known and zooming at the border should feel better.
Hi @GamingMinds-DanielC, thank you very much for the additional information!!! Everything works now!!
This is the updated code:
#include <cstdio>
#include <cstdlib>
#include <opencv2/opencv.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui/imgui.hpp>
static void glfwErrorCallback(int error, const char * description) {
fprintf(stderr, "GLFW error (%d): %s\n", error, description);
}
//****************************************************************************/
// main
//****************************************************************************/
int main(int argc, const char * argv[]) {
glfwSetErrorCallback(glfwErrorCallback);
if(!glfwInit()) {
fprintf(stderr, "GLFW init error\n");
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
glfwWindowHint(GLFW_SAMPLES, 4);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow * window = glfwCreateWindow(800, 600, "Image Test", NULL, NULL);
if(window == NULL) {
fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
fprintf(stderr, "Failed to initialize GLAD\n");
abort();
}
glfwSwapInterval(1);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 150");
ImGui::StyleColorsDark();
cv::Mat image = cv::imread( "/Users/thewoz/Dropbox/Research/COBBS/test/Lena.png", cv::IMREAD_COLOR);
cv::cvtColor(image, image, cv::COLOR_BGR2RGBA);
ImVec2 scroll;
ImVec2 imageSize;
while(!glfwWindowShouldClose(window)) {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGuiViewport * viewport = ImGui::GetMainViewport();
// Set viewport explicitly so GetFrameHeight reacts to DPI changes
ImGui::SetCurrentViewport(nullptr, (ImGuiViewportP*)viewport);
float height = ImGui::GetFrameHeight();
if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("Menu")) {
ImGui::MenuItem("Hello!", NULL, false, true);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if(ImGui::BeginViewportSideBar("StatusBar", viewport, ImGuiDir_Down, height, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::Text("Zoom: 100");
ImGui::SameLine();
ImGui::Text("Frame: 1/1");
ImGui::EndMenuBar();
}
ImGui::End();
}
ImGuiID mainDockSpaceId = ImGui::DockSpaceOverViewport();
ImGuiWindowClass window_class;
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoTabBar;
ImGui::SetNextWindowClass(&window_class);
ImGui::Begin("Down", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ImGui::End();
ImGui::SetNextWindowClass(&window_class);
ImGui::SetNextWindowContentSize(imageSize);
ImGui::SetNextWindowScroll(scroll);
ImGui::Begin("Left", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::GetStyle().WindowPadding.x = 0;
ImGui::GetStyle().WindowPadding.y = 0;
static float imageScale = 1;
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data);
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
//ImGui::SetCursorPos((ImGui::GetWindowSize() - imageSize) * 0.5f);
ImGui::Image(reinterpret_cast<void*>(static_cast<intptr_t>(texture)), imageSize);
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
// If the mouse is over the image
if(ImGui::IsItemHovered()) {
// calculate the position in the mouse in the image reference frame
ImVec2 mousePosInImage;
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y + ImGui::GetScrollY()) / imageScale;
// whether the wheel has been moved
if(ImGui::GetIO().MouseWheel != 0 && ImGui::GetIO().KeyCtrl) {
imageScale = imageScale * pow(1.50, ImGui::GetIO().MouseWheel);
if(imageScale < 0.1) imageScale = 0.1;
if(imageScale > 30) imageScale = 30;
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y - ImGui::GetIO().MousePos.y;
// I update the scroll position
ImGui::SetScrollX(scroll.x);
ImGui::SetScrollY(scroll.y);
}
// if I am dragging the image
if(ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
scroll.x = ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x;
scroll.y = ImGui::GetScrollY() - ImGui::GetIO().MouseDelta.y;
// I update the scroll position
ImGui::SetScrollX(scroll.x);
ImGui::SetScrollY(scroll.y);
}
}
ImGui::End();
ImGui::SetNextWindowClass(&window_class);
ImGui::Begin("Right", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ImGui::End();
static bool sFirstFrame = true;
if(sFirstFrame) {
sFirstFrame = false;
ImGui::DockBuilderRemoveNode(mainDockSpaceId);
ImGui::DockBuilderAddNode(mainDockSpaceId, ImGuiDockNodeFlags_None);
ImGuiID dock_id_up;
ImGuiID dock_id_down;
ImGuiID dock_id_left;
ImGuiID dock_id_right;
ImGui::DockBuilderSplitNode(mainDockSpaceId, ImGuiDir_Up, 0.5f, &dock_id_up, &dock_id_down);
ImGui::DockBuilderSplitNode(dock_id_up, ImGuiDir_Right, 0.5f, &dock_id_right, &dock_id_left);
ImGui::DockBuilderDockWindow("Down", dock_id_down);
ImGui::DockBuilderDockWindow("Left", dock_id_left);
ImGui::DockBuilderDockWindow("Right", dock_id_right);
ImGui::DockBuilderFinish(mainDockSpaceId);
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwPollEvents();
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
Now I'm trying to center the image and I added this line of code (#2212) before the image definition:
ImGui::SetCursorPos((ImGui::GetWindowSize() - imageSize) * 0.5f);
The problem is that now the image is centered but it errs in taking I think the mouse coordinates and thus the scroll position when zooming. I think the problem is because I moved the cursor zero.
The problem is that now the image is centered but it errs in taking I think the mouse coordinates and thus the scroll position when zooming. I think the problem is because I moved the cursor zero.
Yes, the transformation of the mouse position into image space and the calculation of the scroll position assume an unmodified cursor position. If you want to modify that, you need to take that into account as well. But the more you add, the more unwieldy it gets, I would strongly suggest to put your canvas into an object with its own state and handle the reusable stuff in there. Something that could be used f.e. like this:
const ImVec2 imageMin = ImVec2( 0, 0 );
const ImVec2 imageMax = imageSize;
m_Canvas.setContentRect( imageMin, imageMax );
if ( m_Canvas.begin( "canvas", ImVec2( 0, 0 ) ) )
{
ImDrawList* drawList = m_Canvas.getDrawList();
drawList->AddImage( textureId, m_Canvas.contentToScreen( imageMin ), m_Canvas.contentToScreen( imageMax ) );
m_Canvas.end();
}
Hi thank you for your reply. Yes my idea was, after having a working example, to "engineer" it and create an object.
I tried to consider the position of the cursor but with poor success.
I modified the code like this:
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x - ImGui::GetCursorPos().x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y - ImGui::GetCursorPos().y + ImGui::GetScrollY()) / imageScale;
and
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x - ImGui::GetCursorPos().x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y - ImGui::GetCursorPos().y - ImGui::GetIO().MousePos.y;
But it doesn't work... I tried to consider padding as well but it doesn't work. There is something I don't understand about how the reference systems are.
I modified the code like this: ... But it doesn't work...
Looks like a simple sign error. You subtract the cursor position just like the window position when transforming the mouse position into image space, that should be correct. But to calculate the scroll position, you should then ADD the cursor position, just like the window position, not subtract it again.
I am so sorry I put the wrong code. If above I subtract the cursor position below I have to put it back. However this way doesn't work. I was able to calculate the right mouse position on the image by doing this:
ImVec2 cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
ImGui::SetCursorPos(cursorPos);
...
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x - cursorPos.x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y - cursorPos.y + ImGui::GetScrollY()) / imageScale;
While even if I calculate the scroll position in this way. The result is baggy. The zoom no longer works well.
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x + cursorPos.x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y + cursorPos.y - ImGui::GetIO().MousePos.y;
https://github.com/ocornut/imgui/assets/18163083/0ac889a1-0a87-4ad9-9957-a49faea6c5f3
I noticed that even if I don't change ImGui::SetCursorPos
and go back to this version of the code.
I had a problem with the scroll.
That is, as long as I zoom or drag the image the scroll position seems correct. But I can't touch the scroll itself it makes strange jumps and goes back.
https://github.com/ocornut/imgui/assets/18163083/ea46c3d0-207f-4491-86a3-35ae8a326c94
The jumping back after letting go of the scroll bars is easy to fix. You set the scroll position before beginning the window, but you never update it unless you are dragging or scrolling the image. You need to update your scroll with the current scroll values right after beginning the window, that's where the scroll bars take effect.
As for the zooming, try this: adjust the cursor position for the new image size before calculating the scroll position.
Thank you very much for all the suggestions you gave me. I solved the jumping problem in this way. I don't know if that was what you meant, but it works. Here what I did:
ImGui::Begin("Left", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::GetStyle().WindowPadding.x = 0;
ImGui::GetStyle().WindowPadding.y = 0;
[OpenGL Texture Stuff]
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
ImGui::SetCursorPos(cursorPos);
ImGui::Image(reinterpret_cast<void*>(static_cast<intptr_t>(texture)), imageSize);
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
scroll.x = ImGui::GetScrollX();
scroll.y = ImGui::GetScrollY();
// If the mouse is over the image
if(ImGui::IsItemHovered()) {
ImVec2 mousePosInImage;
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x - cursorPos.x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y - cursorPos.y + ImGui::GetScrollY()) / imageScale;
// whether the wheel has been moved
if(ImGui::GetIO().MouseWheel != 0 && ImGui::GetIO().KeyCtrl) {
imageScale = imageScale * pow(1.50, ImGui::GetIO().MouseWheel);
if(imageScale < 0.1) imageScale = 0.1;
if(imageScale > 30) imageScale = 30;
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
//cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x + cursorPos.x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y + cursorPos.y - ImGui::GetIO().MousePos.y;
}
// if I am dragging the image
if(ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
scroll.x = ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x;
scroll.y = ImGui::GetScrollY() - ImGui::GetIO().MouseDelta.y;
}
}
ImGui::End();
However I am really sorry, and I feel so bad, to bother you again. I'm still struggling to fix the zoom with the centered image. I can't quite figure it out. If I uncomment this line zoom no longer works well
cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
I noticed is that, among other things, it no longer lets me scroll to the top left and right once I zoom in on the image.
https://github.com/ocornut/imgui/assets/907951/870fd2b9-b182-4589-a329-5c95e9dee78d
Thank you very much for all the suggestions you gave me. I solved the jumping problem in this way. I don't know if that was what you meant, but it works. Here what I did:
Yes, that's basically what I meant with updating the scroll values after ImGui::Begin()
. There are a few lines in between in your version, but it is functionally equivalent.
I noticed is that, among other things, it no longer lets me scroll to the top left and right once I zoom in on the image.
To be safe, you should calculate cursorPos both times you calculate imageSize, not only when zooming. Window sizes can change after all.
Not being able to scroll to the top left is because your cursor position gets negative values when the image is bigger than the window. When calculating the cursor position (both times) clamp the coordinates to 0 on the low end, then this shouldn't happen anymore.
Another thing that comes to mind: instead of ImGui::GetWindowPos()
, try ImGui::GetContentRegionAvail()
(must be retrieved once before setting the cursor position). That way the size lost to window decorations (in this case scroll bars) gets taken into account as well.
Hello,
Thank you very much again for the replies.
I have done various tests and finally this seems to be the best solution.
I also tried using ImGui::GetContentRegionAvail()
but it doesn't seem to improve the situation.
ImGui::SetNextWindowClass(&window_class);
ImGui::SetNextWindowContentSize(imageSize);
ImGui::SetNextWindowScroll(scroll);
ImGui::Begin("Left", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::GetStyle().WindowPadding.x = 0;
ImGui::GetStyle().WindowPadding.y = 0;
[OpenGL Image Stuff]
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
if(cursorPos.x < 0) cursorPos.x = 0;
if(cursorPos.y < 0) cursorPos.y = 0;
ImGui::SetCursorPos(cursorPos);
ImGui::Image(reinterpret_cast<void*>(static_cast<intptr_t>(texture)), imageSize);
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
scroll.x = ImGui::GetScrollX();
scroll.y = ImGui::GetScrollY();
// If the mouse is over the image
if(ImGui::IsItemHovered()) {
ImVec2 mousePosInImage;
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x - cursorPos.x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y - cursorPos.y + ImGui::GetScrollY()) / imageScale;
// whether the wheel has been moved
if(ImGui::GetIO().MouseWheel != 0 && ImGui::GetIO().KeyCtrl) {
imageScale = imageScale * pow(1.50, ImGui::GetIO().MouseWheel);
if(imageScale < 0.1) imageScale = 0.1;
if(imageScale > 30) imageScale = 30;
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
if(cursorPos.x < 0) cursorPos.x = 0;
if(cursorPos.y < 0) cursorPos.y = 0;
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x + cursorPos.x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y + cursorPos.y - ImGui::GetIO().MousePos.y;
}
// if I am dragging the image
if(ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
scroll.x = ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x;
scroll.y = ImGui::GetScrollY() - ImGui::GetIO().MouseDelta.y;
}
}
ImGui::End();
However, two imperfections remain that I cannot understand.
The first is that the zoom does not follow the mouse until the "scroll bars" in some have moved. In the video there is me trying to zoom in on the yellow hat in the lower right corner. Zooming in the magnification doesn't follow the mouse. So I move the mouse and repeat the operation. What you can see is that when both scroll bars have moved the zoom actually follows the mouse.
https://github.com/ocornut/imgui/assets/907951/83018ac0-1000-44a2-a814-420dd612dada
The second is that zooming in and out of the image there is a strange shift or anyway something doesn't fit.
In the video there is me zooming in on the pupil of the eye.
The mouse is not moved. I.e. the coordinates of ImGui::GetIO().MousePos
always remain the same.
As you can see the zoom does not stay on the pupil but goes around.
https://github.com/ocornut/imgui/assets/907951/7a9763bb-9498-4e7c-b925-a8cbd508141a
In this sped-up video you can see that I don't move the mouse but the image below changes.
https://github.com/ocornut/imgui/assets/907951/6aacb57a-7173-406c-a68f-767b8f446fbf
It must have to do with either cursor position or scroll.
GetWindowPos()
and GetWindowSize()
always remain the same
The first is that the zoom does not follow the mouse until the "scroll bars" in some have moved. In the video there is me trying to zoom in on the yellow hat in the lower right corner. Zooming in the magnification doesn't follow the mouse. So I move the mouse and repeat the operation. What you can see is that when both scroll bars have moved the zoom actually follows the mouse.
ImGui doesn't let you scroll past the content, not even when setting the scroll position programmatically. So as long as the image is smaller than the window, it will stay centered, ignoring the mouse position when zooming. That is normal. If you want a different behavior, you could add a border around your image. That border can be empty space, but it should be independent of your image scale. If you make it f.e. 90% of the window size, you can scroll a tiny image to any edge of the window without it leaving the display area entirely. But you will have more things to keep track of and need to compensate.
The second is that zooming in and out of the image there is a strange shift or anyway something doesn't fit. In the video there is me zooming in on the pupil of the eye. The mouse is not moved. I.e. the coordinates of
ImGui::GetIO().MousePos
always remain the same. As you can see the zoom does not stay on the pupil but goes around.
ImGui often snaps things to pixel positions for better visuals (like f.e. sharp text). This looks like rounding errors accumulating over time. To get rid of this effect, you most likely have to reimplement your canvas with everything you learned, but with a different approach. Don't use the cursor at all, draw manually, round only for display purposes but keep exact values, ... But even without rounding to integers, the floating point format will accumulate errors over time. At some point you will have to stop with "good enough".
Small update:
I also tried using
ImGui::GetContentRegionAvail()
but it doesn't seem to improve the situation.
In the first video of your last post, look at the 10 second timestamp, the top border is bigger than the bottom one because the scroll bars don't get taken into account. Same with the left and right borders at 16 seconds. The effect is there the entire time, just easier to see at those timestamps. Your image is not correctly centered because you use the window size (includes scroll bars) instead of the content region.
Hi @GamingMinds-DanielC Thank you so much for all the help. I understand perfectly what you mean. I think for now the "good enough" for me has come. Maybe in a future, when the rest of the project is finished, I will come back to this part of the code. I attach the latest version of the complete code hoping it will be useful to someone in the future.
#include <cstdio>
#include <cstdlib>
#include <opencv2/opencv.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui/imgui.hpp>
static void glfwErrorCallback(int error, const char * description) {
fprintf(stderr, "GLFW error (%d): %s\n", error, description);
}
//****************************************************************************/
// main
//****************************************************************************/
int main(int argc, const char * argv[]) {
glfwSetErrorCallback(glfwErrorCallback);
if(!glfwInit()) {
fprintf(stderr, "GLFW init error\n");
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
glfwWindowHint(GLFW_SAMPLES, 4);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow * window = glfwCreateWindow(800, 600, "Image Test", NULL, NULL);
if(window == NULL) {
fprintf(stderr, "Failed to create GLFW window\n");
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
fprintf(stderr, "Failed to initialize GLAD\n");
abort();
}
glfwSwapInterval(1);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 150");
ImGui::StyleColorsDark();
cv::Mat image = cv::imread("/Users/thewoz/Dropbox/Research/COBBS/test/Lena.png", cv::IMREAD_COLOR);
cv::cvtColor(image, image, cv::COLOR_BGR2RGBA);
float imageScale = 1;
ImVec2 scroll = ImVec2(0,0);
ImVec2 imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
ImVec2 cursorPos = ImVec2(0,0);
while(!glfwWindowShouldClose(window)) {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGuiViewport * viewport = ImGui::GetMainViewport();
// Set viewport explicitly so GetFrameHeight reacts to DPI changes
ImGui::SetCurrentViewport(nullptr, (ImGuiViewportP*)viewport);
float height = ImGui::GetFrameHeight();
if(ImGui::BeginMainMenuBar()) {
if(ImGui::BeginMenu("Menu")) {
ImGui::MenuItem("Hello!", NULL, false, true);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if(ImGui::BeginViewportSideBar("StatusBar", viewport, ImGuiDir_Down, height, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::Text("Zoom: ");
ImGui::SameLine();
ImGui::Text("Frame: 1/1");
ImGui::EndMenuBar();
}
ImGui::End();
}
ImGuiID mainDockSpaceId = ImGui::DockSpaceOverViewport();
ImGuiWindowClass window_class;
window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoTabBar;
ImGui::SetNextWindowClass(&window_class);
ImGui::Begin("Down", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ImGui::End();
ImGui::SetNextWindowClass(&window_class);
ImGui::SetNextWindowContentSize(imageSize);
ImGui::SetNextWindowScroll(scroll);
ImGui::Begin("Left", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::GetStyle().WindowPadding.x = 0;
ImGui::GetStyle().WindowPadding.y = 0;
GLuint texture;
glGenTextures(1, &texture); // FIXME: There is a memory leak here. It should be done only the first time.
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data);
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
if(cursorPos.x < 0) cursorPos.x = 0;
if(cursorPos.y < 0) cursorPos.y = 0;
ImGui::SetCursorPos(cursorPos);
ImGui::Image(reinterpret_cast<void*>(static_cast<intptr_t>(texture)), imageSize);
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
scroll.x = ImGui::GetScrollX();
scroll.y = ImGui::GetScrollY();
// If the mouse is over the image
if(ImGui::IsItemHovered()) {
ImVec2 mousePosInImage;
mousePosInImage.x = (ImGui::GetIO().MousePos.x - ImGui::GetWindowPos().x - cursorPos.x + ImGui::GetScrollX()) / imageScale;
mousePosInImage.y = (ImGui::GetIO().MousePos.y - ImGui::GetWindowPos().y - cursorPos.y + ImGui::GetScrollY()) / imageScale;
// whether the wheel has been moved
if(ImGui::GetIO().MouseWheel != 0 && ImGui::GetIO().KeyCtrl) {
imageScale = imageScale * pow(1.50, ImGui::GetIO().MouseWheel);
if(imageScale < 0.1) imageScale = 0.1;
if(imageScale < 0.9) imageScale = 0.9;
if(imageScale > 30) imageScale = 30;
imageSize = ImVec2(imageScale * image.size().width, imageScale * image.size().height);
cursorPos = (ImGui::GetWindowSize() - imageSize) * 0.5f;
if(cursorPos.x < 0) cursorPos.x = 0;
if(cursorPos.y < 0) cursorPos.y = 0;
scroll.x = mousePosInImage.x * imageScale + ImGui::GetWindowPos().x + cursorPos.x - ImGui::GetIO().MousePos.x;
scroll.y = mousePosInImage.y * imageScale + ImGui::GetWindowPos().y + cursorPos.y - ImGui::GetIO().MousePos.y;
}
// if I am dragging the image
if(ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
scroll.x = ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x;
scroll.y = ImGui::GetScrollY() - ImGui::GetIO().MouseDelta.y;
}
}
ImGui::End();
ImGui::SetNextWindowClass(&window_class);
ImGui::Begin("Right", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
ImGui::End();
static bool sFirstFrame = true;
if(sFirstFrame) {
sFirstFrame = false;
ImGui::DockBuilderRemoveNode(mainDockSpaceId);
ImGui::DockBuilderAddNode(mainDockSpaceId, ImGuiDockNodeFlags_None);
ImGuiID dock_id_up;
ImGuiID dock_id_down;
ImGuiID dock_id_left;
ImGuiID dock_id_right;
ImGui::DockBuilderSplitNode(mainDockSpaceId, ImGuiDir_Up, 0.5f, &dock_id_up, &dock_id_down);
ImGui::DockBuilderSplitNode(dock_id_up, ImGuiDir_Right, 0.5f, &dock_id_right, &dock_id_left);
ImGui::DockBuilderDockWindow("Down", dock_id_down);
ImGui::DockBuilderDockWindow("Left", dock_id_left);
ImGui::DockBuilderDockWindow("Right", dock_id_right);
ImGui::DockBuilderFinish(mainDockSpaceId);
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwPollEvents();
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
ImGui doesn't let you scroll past the content, not even when setting the scroll position programmatically. So as long as the image is smaller than the window, it will stay centered, ignoring the mouse position when zooming. That is normal. If you want a different behavior, you could add a border around your image.
That's also what you can override with SetNextWindowContentSize()
.
I suppose we could aim to provide an demo applet for this, with zooming and navigation. (While we don't have access to textures in the demo we could manually display a shape of any kind)
Version/Branch of Dear ImGui:
Version 1.90.5, Branch: docking
Back-ends:
ImGui_ImplGlfw ImGui_ImplOpenGL3
Compiler, OS:
macOS
Full config/build information:
No response
Details:
My Issue/Question:
Hi all, I am trying to implement an image navigator/canvas. Something similar to (https://github.com/ocornut/imgui/issues/6051#issuecomment-1398747951) I tried to see what he does but I can't extrapolate it. So I'm starting from scratch. I'm having some problems handling zoom and scroll. What happens is that I have strange jumps that I don't understand. I tried to see if it was a problem related to #2915 but I'm not sure. Specifically what I need is an image viewer where the image is centered (now it is not) where I can move around. Namely zoom and drag.
Screenshots/Video:
https://github.com/ocornut/imgui/assets/907951/9212ae32-4981-4e00-8a42-ea5e8d5b3c5b
Minimal, Complete and Verifiable Example code:
clang++ -std=c++17
pkg-config --cflags opencv4
main.cpppkg-config --libs opencv4
pkg-config --libs --static glfw3
-lglad -framework OpenGL -limgui