Related
Is it possible to zoom (scale) the content of GtkDrawingArea and scroll it with GtkScrolledWindow without changing the drawing area size? My strategy was to use ViewPort and manually adjust the upper horizontal and vertical adjustments to match the content's size. This is useless. It only works if the line gtk widget set size request(area, w * scale, h * scale); is employed in the leftPressed function. In this case, however, the application consumes a large amount of memory and eventually crashes.
To demonstrate, the following code employs left mouse clicks to increase scale (zoom). The application's memory requirements quickly exceed 1GB, and the application crashes.
The question is whether there is a way to scale and scroll the content of the drawing area without resizing it. Such an approach would be much smoother (since it wouldn't require large buffer allocations). Such an approach would be much smoother (because it would not necessitate large buffer allocations).
The example code is written in C and employs GTK 4.6.7. It has been tested on Mac OS X Monterey and Windows 10. In both cases, the application stops responding and crashes after 6-7 left mouse clicks. The use of memory can be easily tracked in activity monitor on a Mac or task manager on a Windows computer.
#include <gtk/gtk.h>
GtkWidget* vp, *sw;
double scale = 1;
int w = 500, h = 300;
GtkAdjustment* ha, * va;
static void leftPressed(GtkGestureClick* gesture, int n_press, double x, double y, GtkWidget* area)
{
scale *= 2;
//zooming should work by setting upper value only, without changing size of drawing area
gtk_adjustment_set_upper(ha, w * scale);
gtk_adjustment_set_upper(va, h * scale);
gtk_widget_set_size_request(area, w * scale, h * scale);
gtk_widget_queue_draw(area);
}
static void rightPressed(GtkGestureClick* gesture, int n_press, double x, double y, GtkWidget* area)
{
scale /= 2;
//gtk_widget_set_size_request(child, w / scale, h / scale);
gtk_adjustment_set_upper(ha, w / scale);
gtk_adjustment_set_upper(va, h / scale);
gtk_widget_queue_draw(area);
}
static void drawRectangles(GtkDrawingArea* drawingarea, cairo_t* cr, int width, int height, gpointer data)
{
cairo_scale(cr, scale, scale);
cairo_set_source_rgb(cr, 1, 0, 0);
cairo_rectangle(cr, 0, 0, 100, 50);
cairo_fill(cr);
cairo_set_source_rgb(cr, 0, 1, 0);
cairo_rectangle(cr, 200, 100, 100, 50);
cairo_fill(cr);
cairo_set_source_rgb(cr, 0, 0, 1);
cairo_rectangle(cr, 400, 250, 100, 50);
cairo_fill(cr);
}
static void createWindow(GtkApplication* app, gpointer data)
{
GtkWidget* window = gtk_application_window_new(app);
gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);
//Drawing Area
GtkWidget* area = gtk_drawing_area_new();
gtk_widget_set_size_request(area, w, h);
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), drawRectangles, NULL, NULL);
//viewport
//ha = gtk_adjustment_new();
ha = gtk_adjustment_new(0, 0, w, 30, 360, 400);
va = gtk_adjustment_new(0, 0, h, 20, 180, 200);
vp = gtk_viewport_new(ha, va);
gtk_viewport_set_child(GTK_VIEWPORT(vp), area);
//Scrolled Window
sw = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), vp);
gtk_scrolled_window_set_max_content_height(GTK_SCROLLED_WINDOW(sw), 4000);
gtk_scrolled_window_set_max_content_width(GTK_SCROLLED_WINDOW(sw), 4000);
ha = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(sw));
va = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
//GtkGesture
GtkGesture* pressElement = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pressElement), GDK_BUTTON_PRIMARY);
gtk_widget_add_controller(area, GTK_EVENT_CONTROLLER(pressElement));
g_signal_connect(pressElement, "pressed", G_CALLBACK(leftPressed), area);
GtkGesture* pressElement2 = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pressElement2), GDK_BUTTON_SECONDARY);
gtk_widget_add_controller(area, GTK_EVENT_CONTROLLER(pressElement2));
g_signal_connect(pressElement2, "pressed", G_CALLBACK(rightPressed), area);
gtk_window_set_child(GTK_WINDOW(window), sw);
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char* argv[])
{
// Create a new application
GtkApplication* app = gtk_application_new("com.example.GtkApplication", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(createWindow), NULL);
return g_application_run(G_APPLICATION(app), argc, argv);
}
I am working on a project where I have to project the data from a camera with a resolution of 640x480 on a 4K screen in portrait mode.
The camera is the Kinect V1 but I will switch to version 2 with a better resolution (1920x1080).
My question is how to change the scale of a texture to display in order to get a correct result.
For the moment, I have managed to display on the entire screen but the image is flattened in width. The ideal would be to keep the proportionality and cut an X width on each side of the image.
I am using SDL with OpenGL, here is the concerned part of the code:
// window creation
auto window = SDL_CreateWindow("Imagine",
x,
y,
0,
0,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_BORDERLESS);
// GL initialization and texture creation
void SdlNuitrackRenderHandler::initTexture(int width, int height)
{
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glOrtho(0, _width, _height, 0, -1.0, 1.0);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glGenTextures(1, &_textureID);
width = power2(width);
height = power2(height);
if (_textureBuffer != 0)
delete[] _textureBuffer;
_textureBuffer = new uint8_t[width * height * 3];
memset(_textureBuffer, 0, sizeof(uint8_t) * width * height * 3);
glBindTexture(GL_TEXTURE_2D, _textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Set texture coordinates [0, 1] and vertexes position
{
_textureCoords[0] = (float) _width / width;
_textureCoords[1] = (float) _height / height;
_textureCoords[2] = (float) _width / width;
_textureCoords[3] = 0.0;
_textureCoords[4] = 0.0;
_textureCoords[5] = 0.0;
_textureCoords[6] = 0.0;
_textureCoords[7] = (float) _height / height;
_vertexes[0] = _width;
_vertexes[1] = _height;
_vertexes[2] = _width;
_vertexes[3] = 0.0;
_vertexes[4] = 0.0;
_vertexes[5] = 0.0;
_vertexes[6] = 0.0;
_vertexes[7] = _height;
}
// Texture rendering
// Render prepared background texture
void SdlNuitrackRenderHandler::renderTexture()
{
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glColor4f(1, 1, 1, 1);
glBindTexture(GL_TEXTURE_2D, _textureID);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, GL_RGB, GL_UNSIGNED_BYTE, _textureBuffer);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, _vertexes);
glTexCoordPointer(2, GL_FLOAT, 0, _textureCoords);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisable(GL_TEXTURE_2D);
}
While I agree with what was written in the comments about this being out-dated OpenGL code, the issue has nothing to do with OpenGL at its heart. You want to draw 1 rectangle with the correct aspect ratio inside another rectangle that has a different aspect ratio. You simply need to know where to place the vertices.
Typically with the TEXTURE_2D texture target, you want your texture coordinates to be 0-1 in both directions, unless you plan to crop the input image. There was a time when textures had to have a width and height that were a power of 2. That hasn't been the case in a very long time. So remove these 2 lines:
width = power2(width);
height = power2(height);
So the first thing is to set those properly:
_textureCoords[0] = 1.0;
_textureCoords[1] = 1.0;
_textureCoords[2] = 1.0;
_textureCoords[3] = 0.0;
_textureCoords[4] = 0.0;
_textureCoords[5] = 0.0;
_textureCoords[6] = 0.0;
_textureCoords[7] = 1.0;
(Consequently, that code is really hard to read and will be a pain to maintain. You should make the texture coordinates (and vertex coordinates) be a struct with an x and y value so it makes sense. Right now it's not obvious that it's 4 sets of 2D coordinates that are (max, max), (max, min), (min, min), (min, max). But I digress.)
Next, to figure out the texture coordinates, you need to know whether the video is going to be scaled to fit the width or the height. To do this, you can figure out
double widthScaleRatio = displayWidth / imageWidth; // <- using this scale will guarantee the width of the new image is the same as the display's width, but might crop the height
double heightScaleRatio = displayHeight / imageHeight; // <- using this scale will guarantee the height of the new image is the same as the display's height but might crop the width
double scale = 1.0;
// If scaling by the widthScaleRatio makes the height too big, use the heightScaleRatio
// Otherwise use the widthScaleRatio
if (imageHeight * widthScaleRatio > displayHeight)
{
scale = heightScaleRatio;
}
else
{
scale = widthScaleRatio;
}
Now scale you width and height by the scale:
double newWidth = imageWidth * scale;
double newHeight = imageHeight * scale;
and set your vertices based on that:
_vertexes[0] = newWidth;
_vertexes[1] = newHeight;
_vertexes[2] = newWidth;
_vertexes[3] = 0.0;
_vertexes[4] = 0.0;
_vertexes[5] = 0.0;
_vertexes[6] = 0.0;
_vertexes[7] = newHeight;
And the same caveat applies to making this code easier to read as with the texture coordinates.
EDIT: Here's a simple program to show how it works:
int main(){
double displayWidth = 2160;
double displayHeight = 4096;
double imageWidth = 640;
double imageHeight = 480;
double widthScaleRatio = displayWidth / imageWidth; // <- using this scale will guarantee the width of the new image is the same as the display's width, but might crop the height
double heightScaleRatio = displayHeight / imageHeight; // <- using this scale will guarantee the height of the new image is the same as the display's height but might crop the width
double scale = 1.0;
// If scaling by the widthScaleRatio makes the height too big, use the heightScaleRatio
// Otherwise use the widthScaleRatio
if (imageHeight * widthScaleRatio > displayHeight)
{
scale = heightScaleRatio;
}
else
{
scale = widthScaleRatio;
}
double newWidth = imageWidth * scale;
double newHeight = imageHeight * scale;
std::cout << "New size = (" << newWidth << ", " << newHeight << ")\n";
}
When I run it, I get:
New size = (2160, 1620)
I am fairly new to OpenGL and I am trying to handle a window resizing event using glm::ortho. I am displaying a simple triangle on screen and I am trying to make it so it keeps the same width/height ratio upon window resizing. However, when I resize the window, the triangle just disappears.
Here is my window resizing handling function:
int width = 800, height = 800;
void windowResized(GLFWwindow* window, int width2, int height2) {
width = width2;
height = height2;
glViewport(0, 0, width, height);
proj_matrix = glm::ortho(0.0f, (float)width, (float)height, 0.0f);
}
Cheers!
I'm trying to draw to a renderbuffer (512x512) that's larger than the screen size (i.e., 320x480).
After doing a glReadPixels, the image looks correct, except once the dimensions of the image exceed that of the screen size- in this example, past 320 horizontal and 480 vertical. What causes this anomaly? Is there something I'm missing?
When the window size is >= the size of the renderbuffer, this code works absolutely fine.
Example image that was rendered to the buffer & glReadPixel'd:
http://img593.imageshack.us/img593/3220/rendertobroke.png
unsigned int canvasFrameBuffer;
bglGenFramebuffers(1, &canvasFrameBuffer);
bglBindFramebuffer(BGL_RENDERBUFFER, canvasFrameBuffer);
// Attach renderbuffer
unsigned int canvasRenderBuffer;
bglGenRenderbuffers(1, &canvasRenderBuffer);
bglBindRenderbuffer(BGL_RENDERBUFFER, canvasRenderBuffer);
bglRenderbufferStorage(BGL_RENDERBUFFER, BGL_RGBA4, width, height);
bglFramebufferRenderbuffer(BGL_FRAMEBUFFER, BGL_COLOR_ATTACHMENT0, BGL_RENDERBUFFER, canvasRenderBuffer);
bglViewport(0, 0, width, height);
Matrix::matrix_t identity, colorMatrix;
Matrix::LoadIdentity(&identity);
Matrix::LoadIdentity(&colorMatrix);
bglClearColor(1.0f, 1.0f, 1.0f, 1.0f);
bglClear(BGL_COLOR_BUFFER_BIT);
Vector::vector_t oldPos, oldScale;
Vector::Copy(&oldPos, &pos);
Vector::Mul(&pos, 0.0f);
Vector::Copy(&oldScale, &scale);
Vector::Load(&scale, 1, 1, 1);
int oldHAlign = halignment;
int oldVAlign = valignment;
halignment = Font::HALIGN_LEFT;
valignment = Font::VALIGN_BOTTOM;
float oldXRatio = vid.xratio;
float oldYRatio = vid.yratio;
vid.xratio = 1;
vid.yratio = 1;
Drawing::Set2D(this->size.x, this->size.y); // glOrtho and setup projection/modelview matrices
Draw(&identity, &colorMatrix);
Vector::Copy(&pos, &oldPos);
Vector::Copy(&scale, &oldScale);
halignment = oldHAlign;
valignment = oldVAlign;
vid.xratio = oldXRatio;
vid.yratio = oldYRatio;
byte *buffer = (byte*)Z_Malloc(width * height * 3, ZT_STATIC);
bglPixelStorei(BGL_PACK_ALIGNMENT, 1);
bglReadPixels(0, 0, width, height, BGL_RGB, BGL_UNSIGNED_BYTE, buffer);
byte *final = RGBtoLuminance(buffer, width, height);
SaveTGA("canvas.tga", final, width, height, 1);
Z_Free(buffer);
// unbind frame buffer
bglBindRenderbuffer(BGL_RENDERBUFFER, 0);
bglBindFramebuffer(BGL_FRAMEBUFFER, 0);
bglDeleteRenderbuffers(1, &canvasRenderBuffer);
bglDeleteFramebuffers(1, &canvasFrameBuffer);
bglViewport(0, 0, vid.width, vid.height);
Here's the answer.
Change this line:
bglBindFramebuffer(BGL_RENDERBUFFER, canvasFrameBuffer);
to this:
bglBindFramebuffer(BGL_FRAMEBUFFER, canvasFrameBuffer);
Despite an earlier question (asked here), our project is constrained to using glDrawPixels, so we have to do some hackery.
One of the feature requirements is to be able to have a magnified view show up on a clicked region of an image; so, looking at an image, I want to click the mouse, and have a 200% image window show up where the mouse is. As I drag my cursor, the window should follow the cursor.
The context is set up like:
The Big Red Book has code that looks like this:
Gl.glShadeModel(Gl.GL_FLAT);
Gl.glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 2);
Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_LINE);
Gl.glDisable(Gl.GL_SCISSOR_TEST);
Gl.glDisable(Gl.GL_ALPHA_TEST);
Gl.glDisable(Gl.GL_STENCIL_TEST);
Gl.glDisable(Gl.GL_DEPTH_TEST);
Gl.glDisable(Gl.GL_BLEND);
Gl.glDisable(Gl.GL_DITHER);
Gl.glDisable(Gl.GL_LOGIC_OP);
Gl.glDisable(Gl.GL_LIGHTING);
Gl.glDisable(Gl.GL_FOG);
Gl.glDisable(Gl.GL_TEXTURE_1D);
Gl.glDisable(Gl.GL_TEXTURE_2D);
Gl.glPixelTransferi(Gl.GL_MAP_COLOR, Gl.GL_TRUE);
Gl.glPixelTransferf(Gl.GL_RED_SCALE, 1.0f);
Gl.glPixelTransferi(Gl.GL_RED_BIAS, 0);
Gl.glPixelTransferf(Gl.GL_GREEN_SCALE, 1.0f);
Gl.glPixelTransferi(Gl.GL_GREEN_BIAS, 0);
Gl.glPixelTransferf(Gl.GL_BLUE_SCALE, 1.0f);
Gl.glPixelTransferi(Gl.GL_BLUE_BIAS, 0);
Gl.glPixelTransferi(Gl.GL_ALPHA_SCALE, 1);
Gl.glPixelTransferi(Gl.GL_ALPHA_BIAS, 0);
And then the call to make the smaller-but-zoomed image looks like
int width = (int)((this.Width * 0.2)/2.0);
Gl.glReadBuffer(Gl.GL_FRONT_AND_BACK);
Gl.glRasterPos2i(0, 0);
Gl.glBitmap(0, 0, 0, 0, mStartX - (width*2), mStartY, null);
Gl.glPixelZoom(2.0f, 2.0f);
Gl.glCopyPixels(mStartX - width, mStartY, width, width, Gl.GL_COLOR);
where mStartY and mStartX are the points where the click happened.
Problem is, the window that shows up is really mangling the lookup tables, and really clamping the image down to essentially a black-and-white binary image (ie, no shades of grey).
The data is a black-and-white unsigned short array, and is set with this code:
float step = (65535.0f / (float)(max - min));
mColorTable = new ushort[65536];
int i;
for (i = 0; i < 65536; i++)
{
if (i < min)
mColorTable[i] = 0;
else if (i > max)
mColorTable[i] = 65535;
else
mColorTable[i] = (ushort)((float)(i - min) * step);
}
.... //some irrelevant code
Gl.glPixelMapusv(Gl.GL_PIXEL_MAP_R_TO_R, 65536, mColorTable);
Gl.glPixelMapusv(Gl.GL_PIXEL_MAP_G_TO_G, 65536, mColorTable);
Gl.glPixelMapusv(Gl.GL_PIXEL_MAP_B_TO_B, 65536, mColorTable);
Now, according to this documentation, I should use GL_PIXEL_MAP_I_TO_I and set INDEX_SCALE and INDEX_BIAS to zero, but doing that does not change the result, that the image is severely clamped. And by 'severely clamped' I mean it's either black or white, with very few shades of grey, but the original non-magnified image looks like what's expected.
So, how do I avoid the clamping of the magnified view? Should I make a second control that follows the cursor and gets filled in with data from the first control? That approach seems like it would take the array copies outside of the graphics card and into C#, which would almost by definition be slower, and so make the control nonresponsive.
Oh, I'm using C# and the Tao framework, if that matters.
Here's the answer. The problem is that the LUT is being applied twice, so before calling the copy, call:
Gl.glPixelTransferi(Gl.GL_MAP_COLOR, Gl.GL_FALSE);
Then, once done, call:
Gl.glPixelTransferi(Gl.GL_MAP_COLOR, Gl.GL_TRUE);
That way, the 'extreme clamping' I was describing is removed.
#thing2k-- your solution causes the copy to happen outside the graphics card, so slows down the drawing on mouse drag, but doesn't fix the double clamp.
Please, pretty please with loads of sugar, molasses, sprinkles and a mist of high-fructose corn syrup on top and all over, explain why you cannot just use texture-mapping to draw this imagery.
Texture-mapping is a core, basic, everyday, run of the mill, garden-variety, standard, typical, expected, and just generally nice feature of OpenGL. It is in version 1.4. Why not use it as a starting point?
If I understand you correctly then this should be close to what you after, using glReadPixels and glDrawPixels.
Sorry it's C++ not C# but the OpenGL function should still be the same.
// main.cpp
// glut Text
#ifdef __WIN32__
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <GL/glut.h>
#include <cstdio>
int WIDTH = 800;
int HEIGHT = 600;
int MouseButton, MouseY = 0, MouseX = 0;
const int size = 80;
char *image, rect[size*size*3];
int imagewidth, imageheight;
bool Init()
{
int offset;
FILE* file = fopen("image.bmp", "rb");
if (file == NULL)
return false;
fseek(file, 10, SEEK_SET);
fread(&offset, sizeof(int), 1, file);
fseek(file, 18, SEEK_SET);
fread(&imagewidth, sizeof(int), 1, file);
fread(&imageheight, sizeof(int), 1, file);
fseek(file, offset, SEEK_SET);
image = new char[imagewidth*imageheight*3];
if (image == NULL)
return false;
fread(image, 1, imagewidth*imageheight*3, file);
fclose(file);
return true;
}
void Reshape(int width, int height)
{
WIDTH = width;
HEIGHT = height;
glViewport(0 , 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width, 0, height);
}
void Display()
{
int size2 = size/2;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRasterPos2i(0,0);
glPixelZoom(1.f, 1.f);
glDrawPixels(imagewidth, imageheight, 0x80E0/*GL_RGB*/, GL_UNSIGNED_BYTE, image);
glReadPixels(MouseX-size2, MouseY-size2, size, size, GL_RGB, GL_UNSIGNED_BYTE, rect);
glPixelZoom(2.f, 2.f);
glRasterPos2i(MouseX-size, MouseY-size);
glDrawPixels(size, size, GL_RGB, GL_UNSIGNED_BYTE, rect);
glFlush();
glutSwapBuffers();
}
void Mouse(int button, int state, int x, int y)
{
if (state == GLUT_DOWN)
MouseButton &= (1<<button);
else
MouseButton &= ~(1<<button);
}
void MouseMove(int x, int y)
{
MouseX = x;
MouseY = HEIGHT - y;
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
if (Init() == false)
return 1;
glutInitWindowSize(WIDTH, HEIGHT);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutCreateWindow("glut_Text");
glClearColor(0.25, 0.25, 0.25, 1.0);
glutReshapeFunc(Reshape);
glutDisplayFunc(Display);
glutIdleFunc(Display);
glutMouseFunc(Mouse);
glutMotionFunc(MouseMove);
glutPassiveMotionFunc(MouseMove);
glutMainLoop();
return 0;
}
Hope this helps.