From ef5d3920db00aabec046dda5678b2cec7c3c2d8e Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 8 Mar 2017 16:34:10 +0100 Subject: [PATCH] move Image viewer to QOpenGLWidget --- src/Mod/Image/Gui/CMakeLists.txt | 25 +- src/Mod/Image/Gui/ImageView.cpp | 5 +- src/Mod/Image/Gui/ImageView.h | 5 + src/Mod/Image/Gui/OpenGLImageBox.cpp | 934 +++++++++++++++++++++++++++ src/Mod/Image/Gui/OpenGLImageBox.h | 122 ++++ 5 files changed, 1087 insertions(+), 4 deletions(-) create mode 100644 src/Mod/Image/Gui/OpenGLImageBox.cpp create mode 100644 src/Mod/Image/Gui/OpenGLImageBox.h diff --git a/src/Mod/Image/Gui/CMakeLists.txt b/src/Mod/Image/Gui/CMakeLists.txt index ee940bbc04..cbeb2eb1af 100644 --- a/src/Mod/Image/Gui/CMakeLists.txt +++ b/src/Mod/Image/Gui/CMakeLists.txt @@ -23,10 +23,19 @@ set(ImageGui_LIBS set(ImageGui_MOC_HDRS ImageView.h - GLImageBox.h ImageOrientationDialog.h ) +if(FREECAD_USE_QTOPENGL_WIDGET) + list(APPEND ImageGui_MOC_HDRS + OpenGLImageBox.h + ) +else() + list(APPEND ImageGui_MOC_HDRS + GLImageBox.h + ) +endif() + fc_wrap_cpp(ImageGui_MOC_SRCS ${ImageGui_MOC_HDRS}) SOURCE_GROUP("Moc" FILES ${ImageGui_MOC_SRCS}) @@ -57,8 +66,6 @@ SET(ImageGui_SRCS ImageOrientationDialog.h ViewProviderImagePlane.cpp ViewProviderImagePlane.h - GLImageBox.cpp - GLImageBox.h Resources/Image.qrc ImageView.cpp ImageView.h @@ -69,6 +76,18 @@ SET(ImageGui_SRCS XpmImages.h ) +if(FREECAD_USE_QTOPENGL_WIDGET) + list(APPEND ImageGui_SRCS + OpenGLImageBox.cpp + OpenGLImageBox.h + ) +else() + list(APPEND ImageGui_SRCS + GLImageBox.cpp + GLImageBox.h + ) +endif() + add_library(ImageGui SHARED ${ImageGui_SRCS}) target_link_libraries(ImageGui ${ImageGui_LIBS}) diff --git a/src/Mod/Image/Gui/ImageView.cpp b/src/Mod/Image/Gui/ImageView.cpp index 23209f6d6f..aa9dfb1e2c 100644 --- a/src/Mod/Image/Gui/ImageView.cpp +++ b/src/Mod/Image/Gui/ImageView.cpp @@ -28,7 +28,6 @@ #endif #include "ImageView.h" -#include "GLImageBox.h" #include "../App/ImageBase.h" #include "XpmImages.h" @@ -65,7 +64,11 @@ ImageView::ImageView(QWidget* parent) // Since Qt the class QGLWidget is marked as deprecated and should be // replaced by QOpenGLWidget. +#if defined(HAVE_QT5_OPENGL) + _pGLImageBox = new GLImageBox(this); +#else _pGLImageBox = new GLImageBox(parent); +#endif // HAVE_QT5_OPENGL #else _pGLImageBox = new GLImageBox(this); #endif diff --git a/src/Mod/Image/Gui/ImageView.h b/src/Mod/Image/Gui/ImageView.h index 092ce9824d..aed86b7dae 100644 --- a/src/Mod/Image/Gui/ImageView.h +++ b/src/Mod/Image/Gui/ImageView.h @@ -19,7 +19,12 @@ #define ImageView_H #include +#include +#if defined(HAVE_QT5_OPENGL) +#include "OpenGLImageBox.h" +#else #include "GLImageBox.h" +#endif class QSlider; class QAction; diff --git a/src/Mod/Image/Gui/OpenGLImageBox.cpp b/src/Mod/Image/Gui/OpenGLImageBox.cpp new file mode 100644 index 0000000000..7aeaa06ec8 --- /dev/null +++ b/src/Mod/Image/Gui/OpenGLImageBox.cpp @@ -0,0 +1,934 @@ +/*************************************************************************** + * * + * This is a QGLWidget displaying an image or portion of an image in a * + * box. * + * * + * Author: Graeme van der Vlugt * + * Copyright: Imetric 3D GmbH * + * Year: 2004 * + * * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * for detail see the LICENCE text file. * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#if defined(__MINGW32__) +# include +# include +#elif defined (FC_OS_MACOSX) +# include +# include +#elif defined (FC_OS_WIN32) +# include +# include +# include +#else +# include +# include +#endif + +#include "OpenGLImageBox.h" + +using namespace ImageGui; + +#if defined(Q_CC_MSVC) +#pragma warning(disable:4305) // init: truncation from const double to float +#endif + +bool GLImageBox::haveMesa = false; + +/* +Notes: ++ Using QGLWidget with Qt5 still works fine ++ But QGLWidget is marked as deprecated and should be replaced with QOpenGLWidget ++ When opening one or more image views (based on QOpenGLWidget) everything works fine + but as soon as a 3d view based on QGLWidget is opened the content becomes black and + from then on will never render normally again. ++ This problem is caused by QuarterWidget::paintEvent!!! ++ https://groups.google.com/forum/?_escaped_fragment_=topic/coin3d-discuss/2SVG6ZxOWy4#!topic/coin3d-discuss/2SVG6ZxOWy4 + ++ Using a QSurfaceFormat to switch on double buffering doesn't seem to have any effect + QSurfaceFormat format; + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + setFormat(format); ++ Directly swapping in paintGL doesn't work either + QOpenGLContext::currentContext()->swapBuffers(QOpenGLContext::currentContext()->surface()); ++ Check for OpenGL errors with: GLenum err = glGetError(); // GL_NO_ERROR ++ http://retokoradi.com/2014/04/21/opengl-why-is-your-code-producing-a-black-window/ ++ http://forum.openscenegraph.org/viewtopic.php?t=15177 ++ implement GLImageBox::renderText ++ See http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html +*/ + +/* TRANSLATOR ImageGui::GLImageBox */ + +// Constructor +GLImageBox::GLImageBox(QWidget * parent, Qt::WindowFlags f) + : QOpenGLWidget(parent, f) +{ + // uses default display format for the OpenGL rendering context + // (double buffering is enabled) + + // enable mouse tracking when moving even if no buttons are pressed + setMouseTracking(true); + + // initialise variables + _x0 = 0; + _y0 = 0; + _zoomFactor = 1.0; + _base_x0 = 0; + _base_y0 = 0; + _pColorMap = 0; + _numMapEntries = 0; + +#ifdef _DEBUG + QSurfaceFormat format; + format.setProfile(QSurfaceFormat::CoreProfile); + format.setOption(QSurfaceFormat::DebugContext); + this->setFormat(format); +#endif +} + + +// Destructor +GLImageBox::~GLImageBox() +{ + delete [] _pColorMap; +} + +void GLImageBox::handleLoggedMessage(const QOpenGLDebugMessage &message) +{ + qDebug() << message; +} + +// Set up the OpenGL rendering state +void GLImageBox::initializeGL() +{ + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + //QColor c(Qt::black); + QPalette p = this->palette(); + QColor c(p.color(this->backgroundRole())); // Let OpenGL clear to black + f->glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF()); // Let OpenGL clear to black + static bool init = false; + if (!init) { + init = true; + std::string ver = (const char*)(glGetString(GL_VERSION)); + haveMesa = (ver.find("Mesa") != std::string::npos); + } + +#if _DEBUG +#if 0 + QString ext = QString::fromLatin1((const char*)(glGetString(GL_EXTENSIONS))); + QStringList list = ext.split(QLatin1Char(' ')); + std::list extlist; + Q_FOREACH(QString it, list) { + extlist.push_back(it.toStdString()); + } + std::string glRenderer = (const char*)(glGetString(GL_RENDERER)); + std::string glVendor = (const char*)(glGetString(GL_VENDOR)); + std::string glVersion = (const char*)(glGetString(GL_VERSION)); +#endif + + QOpenGLContext *context = QOpenGLContext::currentContext(); + if (context->hasExtension(QByteArrayLiteral("GL_KHR_debug"))) { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); + connect(logger, &QOpenGLDebugLogger::messageLogged, this, &GLImageBox::handleLoggedMessage); + + if (logger->initialize()) + logger->startLogging(); + } +#endif +} + + +// Update the viewport +void GLImageBox::resizeGL( int w, int h ) +{ + glViewport( 0, 0, (GLint)w, (GLint)h ); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + gluOrtho2D(0, width() - 1, height() - 1, 0); + glMatrixMode(GL_MODELVIEW); +} + +// Redraw (current image) +void GLImageBox::redraw() +{ + update(); +} + + +// Paint the box +void GLImageBox::paintGL() +{ + glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_TEST); + + // clear background (in back buffer) + //glDrawBuffer(GL_BACK); // this is an invalid call! + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + + // Draw the image + drawImage(); + + // Emit a signal for owners to draw any graphics that is needed. + if (_image.hasValidData() == true) + drawGraphics(); + + // flush the OpenGL graphical pipeline + glFinish(); + glPopAttrib(); + + // Double buffering is used so we need to swap the buffers + // There is no need to explicitly call this function because it is + // done automatically after each widget repaint, i.e. each time after paintGL() has been executed + // swapBuffers(); +} + +// Draw the image +void GLImageBox::drawImage() +{ + if (_image.hasValidData() == false) + return; + + // Gets the size of the diplayed image area using the current display settings + // (in units of image pixels) + int dx, dy; + getDisplayedImageAreaSize(dx, dy); + + // Draw the visible image region with the correct position and zoom + if ((dx > 0) && (dy > 0)) + { + // Get top left image pixel to display + int tlx = std::max(0, _x0); + int tly = std::max(0, _y0); + + // Get pointer to first pixel in source image rectangle + unsigned char* pPix = (unsigned char *)(_image.getPixelDataPtr()); + pPix += (unsigned long)(_image.getNumBytesPerPixel()) * (tly * _image.getWidth() + tlx); + + // Draw in the back buffer, using the following parameters + //glDrawBuffer(GL_BACK); // this is an invalid call! + glPixelStorei(GL_UNPACK_ROW_LENGTH, _image.getWidth()); // defines number of pixels in a row + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // defines byte alignment of rows + glPixelZoom(_zoomFactor, -_zoomFactor); // defines the zoom factors to draw at + + // set current raster position to coincide with top left image pixel to display + // the first pixel is always displayed in full when zoomed in + // round to nearest widget pixel that coincides with top left corner of top left image pixel to display + int xx = (int)floor(ICToWC_X(tlx - 0.5) + 0.5); + int yy = (int)floor(ICToWC_Y(tly - 0.5) + 0.5); + glRasterPos2f(xx, yy); + + // Compute scale to stretch number of significant bits to full range + // e.g. stretch 12 significant bits to 16-bit range: 0-4095 -> 0-65535, therefore scale = 65535/4095 + double scale = (pow(2.0, _image.getNumBitsPerSample()) - 1.0) / (pow(2.0, _image.getNumSigBitsPerSample()) - 1.0); + glPixelTransferf(GL_RED_SCALE, (float)scale); + glPixelTransferf(GL_GREEN_SCALE, (float)scale); + glPixelTransferf(GL_BLUE_SCALE, (float)scale); + + // Load the color map if present + if (_pColorMap != 0) + { + if (!haveMesa) glPixelTransferf(GL_MAP_COLOR, 1.0); + glPixelMapfv(GL_PIXEL_MAP_R_TO_R, _numMapEntries, _pColorMap); + glPixelMapfv(GL_PIXEL_MAP_G_TO_G, _numMapEntries, _pColorMap + _numMapEntries); + glPixelMapfv(GL_PIXEL_MAP_B_TO_B, _numMapEntries, _pColorMap + _numMapEntries * 2); + glPixelMapfv(GL_PIXEL_MAP_A_TO_A, _numMapEntries, _pColorMap + _numMapEntries * 3); + } + else + { + glPixelTransferf(GL_MAP_COLOR, 0.0); + glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 0, NULL); + glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 0, NULL); + glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 0, NULL); + glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 0, NULL); + } + + // Get the pixel format + GLenum pixFormat; + GLenum pixType; + getPixFormat(pixFormat, pixType); + + // Draw the defined source rectangle + glDrawPixels(dx, dy, pixFormat, pixType, (GLvoid *)pPix); + glFlush(); + } +} + +// Gets the size of the diplayed image area using the current display settings +// (in units of image pixels) +void GLImageBox::getDisplayedImageAreaSize(int &dx, int &dy) +{ + if (_image.hasValidData() == false) + { + dx = 0; + dy = 0; + } + else + { + // Make sure drawing position and zoom factor are valid + limitCurrPos(); + limitZoomFactor(); + + // Image coordinates of top left widget pixel = (_x0, _y0) + // Get image coordinates of bottom right widget pixel + int brx = (int)ceil(WCToIC_X(width() - 1)); + int bry = (int)ceil(WCToIC_Y(height() - 1)); + + // Find the outer coordinates of the displayed image area + int itlx = std::max(_x0, 0); + int itly = std::max(_y0, 0); + int ibrx = std::min(brx, (int)(_image.getWidth()) - 1); + int ibry = std::min(bry, (int)(_image.getHeight()) - 1); + if ((itlx >= (int)(_image.getWidth())) || + (itly >= (int)(_image.getHeight())) || + (ibrx < 0) || + (ibry < 0)) + { + dx = 0; + dy = 0; + } + dx = ibrx - itlx + 1; + dy = ibry - itly + 1; + } +} + +// Gets the value of an image sample at the given image pixel position +// Returns 0 for valid value or -1 if coordinates or sample index are out of range or +// if there is no image data +int GLImageBox::getImageSample(int x, int y, unsigned short sampleIndex, double &value) +{ + return (_image.getSample(x, y, sampleIndex, value)); +} + +// Gets the number of samples per pixel for the image +unsigned short GLImageBox::getImageNumSamplesPerPix() +{ + return (_image.getNumSamples()); +} + +// Gets the format (color space format) of the image +int GLImageBox::getImageFormat() +{ + return (_image.getFormat()); +} + + +// Get the OpenGL pixel format and pixel type from the image properties +void GLImageBox::getPixFormat(GLenum &pixFormat, GLenum &pixType) +{ + switch(_image.getFormat()) + { + case IB_CF_GREY8: + pixFormat = GL_LUMINANCE; + pixType = GL_UNSIGNED_BYTE; + break; + case IB_CF_GREY16: + pixFormat = GL_LUMINANCE; + pixType = GL_UNSIGNED_SHORT; + break; + case IB_CF_GREY32: + pixFormat = GL_LUMINANCE; + pixType = GL_UNSIGNED_INT; + break; + case IB_CF_RGB24: + pixFormat = GL_RGB; + pixType = GL_UNSIGNED_BYTE; + break; +#ifndef FC_OS_CYGWIN + case IB_CF_BGR24: + pixFormat = GL_BGR_EXT; + pixType = GL_UNSIGNED_BYTE; + break; + case IB_CF_RGB48: + pixFormat = GL_RGB; + pixType = GL_UNSIGNED_SHORT; + break; + case IB_CF_BGR48: + pixFormat = GL_BGR_EXT; + pixType = GL_UNSIGNED_SHORT; + break; +#endif + case IB_CF_RGBA32: + pixFormat = GL_RGBA; + pixType = GL_UNSIGNED_BYTE; + break; + case IB_CF_RGBA64: + pixFormat = GL_RGBA; + pixType = GL_UNSIGNED_SHORT; + break; +#ifndef FC_OS_CYGWIN + case IB_CF_BGRA32: + pixFormat = GL_BGRA_EXT; + pixType = GL_UNSIGNED_BYTE; + break; + case IB_CF_BGRA64: + pixFormat = GL_BGRA_EXT; + pixType = GL_UNSIGNED_SHORT; + break; +#endif + default: + // Should never happen + pixFormat = GL_LUMINANCE; + pixType = GL_UNSIGNED_BYTE; + QMessageBox::warning((QWidget *)this, tr("Image pixel format"), + tr("Undefined type of colour space for image viewing")); + return; + } +} + +// Limits the current position (centre of top left image pixel) +// Currently we don't limit it! +void GLImageBox::limitCurrPos() +{ + if (_image.hasValidData() == false) + return; + + /* + if (_x0 < 0) + _x0 = 0; + else if (_x0 >= (int)(_image.getWidth())) + _x0 = _image.getWidth() - 1; + if (_y0 < 0) + _y0 = 0; + else if (_y0 >= (int)(_image.getHeight())) + _y0 = _image.getHeight() - 1; + */ +} + +// Limits the current zoom factor from 1:64 to 64:1 +void GLImageBox::limitZoomFactor() +{ + if (_zoomFactor > 64.0) + _zoomFactor = 64.0; + else if (_zoomFactor < (1.0 / 64.0)) + _zoomFactor = 1.0 / 64.0; +} + +// Set the current position (centre of top left image pixel coordinates) +// This function does not redraw (call redraw afterwards) +void GLImageBox::setCurrPos(int x0, int y0) +{ + _x0 = x0; + _y0 = y0; + limitCurrPos(); +} + +// Fixes a base position at the current position +void GLImageBox::fixBasePosCurr() +{ + if (_image.hasValidData() == false) + { + _base_x0 = 0; + _base_y0 = 0; + } + else + { + _base_x0 = _x0; + _base_y0 = _y0; + } +} + +// Set the current zoom factor +// Option to centre the zoom at a given image point or not +// This function does not redraw (call redraw afterwards) +void GLImageBox::setZoomFactor(double zoomFactor, bool useCentrePt, int ICx, int ICy) +{ + if ((useCentrePt == false) || (_image.hasValidData() == false)) + { + _zoomFactor = zoomFactor; + limitZoomFactor(); + } + else + { + // Set new zoom factor + _zoomFactor = zoomFactor; + limitZoomFactor(); + + // get centre position of widget in image coordinates + int ix, iy; + getCentrePoint(ix, iy); + + // try to shift the current position so that defined centre point is in the middle of the widget + // (this can be modified by the limitCurrPos function) + setCurrPos(_x0 - ix + ICx, _y0 - iy + ICy); + } +} + +// Stretch or shrink the image to fit the view (although the zoom factor is limited so a +// very small or very big image may not fit completely (depending on the size of the view) +// This function redraws +void GLImageBox::stretchToFit() +{ + if (_image.hasValidData() == false) + return; + + setToFit(); + update(); +} + +// Sets the settings needed to fit the image into the view (although the zoom factor is limited so a +// very small or very big image may not fit completely (depending on the size of the view) +// This function does not redraw (call redraw afterwards) +void GLImageBox::setToFit() +{ + if (_image.hasValidData() == false) + return; + + // Compute ideal zoom factor to fit the image + double zoomX = (double)width() / (double)(_image.getWidth()); + double zoomY = (double)height() / (double)(_image.getHeight()); + if (zoomX > zoomY) + _zoomFactor = zoomY; + else + _zoomFactor = zoomX; + limitZoomFactor(); + + // set current position to top left image pixel + setCurrPos(0, 0); +} + +// Sets the normal viewing position and zoom = 1 +// If the image is smaller than the widget then the image is centred +// otherwise we view the top left part of the image +// This function does not redraw (call redraw afterwards) +void GLImageBox::setNormal() +{ + if (_image.hasValidData() == false) + return; + + if (((int)(_image.getWidth()) < width()) && ((int)(_image.getHeight()) < height())) + { + setZoomFactor(1.0, true, _image.getWidth() / 2, _image.getHeight() / 2); + } + else + { + _zoomFactor = 1; + setCurrPos(0, 0); + } +} + +// Gets the image coordinates of the centre point of the widget +void GLImageBox::getCentrePoint(int &ICx, int &ICy) +{ + ICx = (int)floor(WCToIC_X((double)(width() - 1) / 2.0) + 0.5); + ICy = (int)floor(WCToIC_Y((double)(height() - 1) / 2.0) + 0.5); +} + +// Moves the image by a relative amount (in widget pixel units) from the base position +// First use fixBasePosCurr() to fix the base position at a position +void GLImageBox::relMoveWC(int WCdx, int WCdy) +{ + double ICdx = WCdx / _zoomFactor; + double ICdy = WCdy / _zoomFactor; + setCurrPos(_base_x0 - (int)floor(ICdx + 0.5), _base_y0 - (int)floor(ICdy + 0.5)); + update(); +} + +// Computes an image x-coordinate from the widget x-coordinate +// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget +// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will +// theoretically coincide with widget coordinate (-0.5,-0.5) +// Zoom = 4: Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375) +// Zoom = 2: Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250) +// Zoom = 1: Widget(0,0) = Image(_x0,_y0) +// Zoom = 0.5: Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500) +// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500) +double GLImageBox::WCToIC_X(double WidgetX) +{ + return ((double)_x0 - 0.5 + (WidgetX + 0.5) / _zoomFactor); +} + +// Computes an image y-coordinate from the widget y-coordinate +// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget +// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will +// theoretically coincide with widget coordinate (-0.5,-0.5) +// Zoom = 4: Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375) +// Zoom = 2: Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250) +// Zoom = 1: Widget(0,0) = Image(_x0,_y0) +// Zoom = 0.5: Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500) +// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500) +double GLImageBox::WCToIC_Y(double WidgetY) +{ + return ((double)_y0 - 0.5 + (WidgetY + 0.5) / _zoomFactor); +} + +// Computes a widget x-coordinate from an image x-coordinate +// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget +// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will +// theoretically coincide with widget coordinate (-0.5,-0.5) +// Zoom = 4: Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375) +// Zoom = 2: Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250) +// Zoom = 1: Widget(0,0) = Image(_x0,_y0) +// Zoom = 0.5: Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500) +// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500) +double GLImageBox::ICToWC_X(double ImageX) +{ + return ((ImageX - (double)_x0 + 0.5) * _zoomFactor - 0.5); +} + +// Computes a widget y-coordinate from an image y-coordinate +// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget +// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will +// theoretically coincide with widget coordinate (-0.5,-0.5) +// Zoom = 4: Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375) +// Zoom = 2: Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250) +// Zoom = 1: Widget(0,0) = Image(_x0,_y0) +// Zoom = 0.5: Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500) +// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500) +double GLImageBox::ICToWC_Y(double ImageY) +{ + return ((ImageY - (double)_y0 + 0.5) * _zoomFactor - 0.5); +} + + +// Clears the image data +void GLImageBox::clearImage() +{ + _image.clear(); + resetDisplay(); +} + +// Load image by copying the pixel data +// The image object will take ownership of the copied pixel data +// (the source image is still controlled by the caller) +// If numSigBitsPerSample = 0 then the full range is assumed to be significant +// displayMode ... controls the initial display of the image, one of: +// IV_DISPLAY_NOCHANGE ... no change to view settings when displaying a new image +// IV_DISPLAY_FITIMAGE ... fit-image when displaying a new image (other settings remain the same) +// IV_DISPLAY_RESET ... reset settings when displaying a new image (image will be displayed at 1:1 scale with no color map) +// This function does not redraw (call redraw afterwards) +// Returns: +// 0 for OK +// -1 for invalid color format +// -2 for memory allocation error +int GLImageBox::createImageCopy(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, int displayMode) +{ + // Copy image + int ret = _image.createCopy(pSrcPixelData, width, height, format, numSigBitsPerSample); + + // Set display settings depending on mode + if (displayMode == IV_DISPLAY_RESET) + { + // reset drawing settings (position, scale, colour mapping) if requested + resetDisplay(); + } + else if (displayMode == IV_DISPLAY_FITIMAGE) + { + // compute stretch to fit settings + setToFit(); + } + else // if (displayMode == IV_DISPLAY_NOCHANGE) + { + // use same settings + limitCurrPos(); + limitZoomFactor(); + } + return ret; +} + +// Make the image object point to another image source +// If takeOwnership is false then: +// This object will not own (control) or copy the pixel data +// (the source image is still controlled by the caller) +// Else if takeOwnership is true then: +// This object will take ownership (control) of the pixel data +// (the source image is not (should not be) controlled by the caller anymore) +// In this case the memory must have been allocated with the new operator (because this class will use the delete operator) +// If numSigBitsPerSample = 0 then the full range is assumed to be significant +// displayMode ... controls the initial display of the image, one of: +// IV_DISPLAY_NOCHANGE ... no change to view settings when displaying a new image +// IV_DISPLAY_FITIMAGE ... fit-image when displaying a new image (other settings remain the same) +// IV_DISPLAY_RESET ... reset settings when displaying a new image (image will be displayed at 1:1 scale with no color map) +// This function does not redraw (call redraw afterwards) +// Returns: +// 0 for OK +// -1 for invalid color format +int GLImageBox::pointImageTo(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, bool takeOwnership, int displayMode) +{ + // Point to image + int ret = _image.pointTo(pSrcPixelData, width, height, format, numSigBitsPerSample, takeOwnership); + + // Set display settings depending on mode + if (displayMode == IV_DISPLAY_RESET) + { + // reset drawing settings (position, scale, colour mapping) if requested + resetDisplay(); + } + else if (displayMode == IV_DISPLAY_FITIMAGE) + { + // compute stretch to fit settings + setToFit(); + } + else // if (displayMode == IV_DISPLAY_NOCHANGE) + { + // use same settings + limitCurrPos(); + limitZoomFactor(); + } + return ret; +} + +// Reset display settings +void GLImageBox::resetDisplay() +{ + clearColorMap(); + setNormal(); // re-draws as well +} + +// Clears the color map +void GLImageBox::clearColorMap() +{ + delete [] _pColorMap; + _pColorMap = 0; + _numMapEntries = 0; +} + +// Calculate the number of color map entries to use +int GLImageBox::calcNumColorMapEntries() +{ + // Get the maximum number of map entries that the system supports + // Get the number of bits per sample for the image if it exists and compute the number of pixel values + // Return the fewer amount of entries + GLint maxMapEntries; + glGetIntegerv(GL_MAX_PIXEL_MAP_TABLE, &maxMapEntries); + int NumEntries = maxMapEntries; + if (_image.hasValidData() == true) + NumEntries = (int)std::min(pow(2.0, (double)(_image.getNumSigBitsPerSample())), (double)maxMapEntries); + return NumEntries; +} + +// Creates a color map (All red entries come first, then green, then blue, then alpha) +// returns 0 for OK, -1 for memory allocation error +// numRequestedEntries ... requested number of map entries (used if not greater than maximum possible or number of intensity values) +// Initialise ... flag to initialise the map to a linear scale or not +int GLImageBox::createColorMap(int numEntriesReq, bool Initialise) +{ + // Get the number of map entries to use + int maxNumEntries = calcNumColorMapEntries(); + int numEntries; + if (numEntriesReq <= 0) + numEntries = maxNumEntries; + else + numEntries = std::min(numEntriesReq, maxNumEntries); + + // Clear and re-create the color map if it's not the desired size + if (numEntries != _numMapEntries) + { + clearColorMap(); + _numMapEntries = numEntries; + + // Create the color map (RGBA) + try + { + _pColorMap = new float[4 * _numMapEntries]; + } + catch(...) + { + clearColorMap(); + return -1; + } + } + + // Initialise the color map if requested + // (All red entries come first, then green, then blue, then alpha) + if (Initialise == true) + { + // For each RGB channel + int arrayIndex = 0; + for (int chan = 0; chan < 3; chan++) + { + for (int in = 0; in < _numMapEntries; in++) + { + _pColorMap[arrayIndex] = (float)in / (float)(_numMapEntries - 1); + arrayIndex++; + } + } + // For alpha channel + for (int in = 0; in < _numMapEntries; in++) + { + _pColorMap[arrayIndex] = 1.0; + arrayIndex++; + } + } + + return 0; +} + +// Sets a color map RGBA value +// (All red entries come first, then green, then blue, then alpha) +// index ... index of color map RGBA entry +// red ... intensity value for this red entry (range 0 to 1) +// green ... intensity value for this green entry (range 0 to 1) +// blue ... intensity value for this blue entry (range 0 to 1) +// alpha ... value for this alpha entry (range 0 to 1) +int GLImageBox::setColorMapRGBAValue(int index, float red, float green, float blue, float alpha) +{ + if ((index < 0) || (index >= _numMapEntries) || + (red < 0.0) || (red > 1.0) || + (green < 0.0) || (green > 1.0) || + (blue < 0.0) || (blue > 1.0) || + (alpha < 0.0) || (alpha > 1.0)) + return -1; + + _pColorMap[index] = red; + _pColorMap[_numMapEntries + index] = green; + _pColorMap[_numMapEntries * 2 + index] = blue; + _pColorMap[_numMapEntries * 3 + index] = alpha; + return 0; +} + +// Sets a color map red value +// (All red entries come first, then green, then blue, then alpha) +// index ... index of color map red entry +// value ... intensity value for this red entry (range 0 to 1) +int GLImageBox::setColorMapRedValue(int index, float value) +{ + if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0)) + return -1; + + _pColorMap[index] = value; + return 0; +} + +// Sets a color map green value +// (All red entries come first, then green, then blue, then alpha) +// index ... index of color map green entry +// value ... intensity value for this green entry (range 0 to 1) +int GLImageBox::setColorMapGreenValue(int index, float value) +{ + if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0)) + return -1; + + _pColorMap[_numMapEntries + index] = value; + return 0; +} + +// Sets a color map blue value +// (All red entries come first, then green, then blue, then alpha) +// index ... index of color map blue entry +// value ... intensity value for this blue entry (range 0 to 1) +int GLImageBox::setColorMapBlueValue(int index, float value) +{ + if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0)) + return -1; + + _pColorMap[_numMapEntries * 2 + index] = value; + return 0; +} + +// Sets a color map alpha value +// (All red entries come first, then green, then blue, then alpha) +// index ... index of color map alpha entry +// value ... value for this alpha entry (range 0 to 1) +int GLImageBox::setColorMapAlphaValue(int index, float value) +{ + if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0)) + return -1; + + _pColorMap[_numMapEntries * 3 + index] = value; + return 0; +} + +// Helper function to convert a pixel's value (of a sample) to the color map index (i.e. the map index that will be used for that pixel value) +unsigned int GLImageBox::pixValToMapIndex(double PixVal) +{ + if (_pColorMap != NULL) + { + double MaxVal = pow(2.0, _image.getNumBitsPerSample()) - 1.0; + double Scale = (pow(2.0, _image.getNumBitsPerSample()) - 1.0) / (pow(2.0, _image.getNumSigBitsPerSample()) - 1.0); + double PixVal01 = Scale * PixVal / MaxVal; + int numMapEntries = getNumColorMapEntries(); + unsigned int MapIndex = (unsigned int)floor(0.5 + PixVal01 * (double)(numMapEntries - 1)); + return MapIndex; + } + else + { + return 0; + } +} + +void GLImageBox::renderText(int x, int y, const QString& str, const QFont& fnt) +{ + // replacement for QGLWidget::renderText + // FIXME: The result looks very poor! + + if (str.isEmpty() || !isValid()) + return; + + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + + GLint view[4]; + bool use_scissor_testing = f->glIsEnabled(GL_SCISSOR_TEST); + if (!use_scissor_testing) + f->glGetIntegerv(GL_VIEWPORT, &view[0]); + + + glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); + glPushAttrib(GL_ALL_ATTRIB_BITS); + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + glShadeModel(GL_FLAT); + glDisable(GL_CULL_FACE); + glDisable(GL_LIGHTING); + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + QRect viewport(view[0], view[1], view[2], view[3]); + if (!use_scissor_testing && viewport != rect()) { + // if the user hasn't set a scissor box, we set one that + // covers the current viewport + f->glScissor(view[0], view[1], view[2], view[3]); + f->glEnable(GL_SCISSOR_TEST); + } else if (use_scissor_testing) { + // use the scissor box set by the user + f->glEnable(GL_SCISSOR_TEST); + } + + GLfloat color[4]; + f->glGetFloatv(GL_CURRENT_COLOR, &color[0]); + QColor col; + col.setRgbF(color[0], color[1], color[2],color[3]); + + QPainter painter(this); + painter.setPen(col); + painter.setFont(fnt); + painter.drawText(x, y, str); + painter.end(); + + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glPopAttrib(); + glPopClientAttrib(); +} + +#include "moc_OpenGLImageBox.cpp" diff --git a/src/Mod/Image/Gui/OpenGLImageBox.h b/src/Mod/Image/Gui/OpenGLImageBox.h new file mode 100644 index 0000000000..aa5f29fbee --- /dev/null +++ b/src/Mod/Image/Gui/OpenGLImageBox.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * * + * This is a QOpenGLWidget displaying an image or portion of an image * + * in a box. * + * * + * Author: Graeme van der Vlugt * + * Copyright: Imetric 3D GmbH * + * Year: 2004 * + * * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * for detail see the LICENCE text file. * + * * + ***************************************************************************/ + +#ifndef OPENGLIMAGEBOX_H +#define OPENGLIMAGEBOX_H + +#include +#include + +class QOpenGLDebugMessage; + +namespace ImageGui +{ + +#define IV_DISPLAY_NOCHANGE 0 // no change to view settings when displaying a new image +#define IV_DISPLAY_FITIMAGE 1 // fit-image when displaying a new image (other settings remain the same) +#define IV_DISPLAY_RESET 2 // reset settings when displaying a new image (image will be displayed at 1:1 scale with no color map) + +class ImageGuiExport GLImageBox : public QOpenGLWidget +{ + Q_OBJECT + +public: + + GLImageBox(QWidget * parent = 0, Qt::WindowFlags f = 0); + ~GLImageBox(); + + Image::ImageBase *getImageBasePtr() { return &_image; } + + void redraw(); + + int getImageSample(int x, int y, unsigned short sampleIndex, double &value); + unsigned short getImageNumSamplesPerPix(); + int getImageFormat(); + + void fixBasePosCurr(); + double getZoomFactor() { return _zoomFactor; } + void setZoomFactor(double zoomFactor, bool useCentrePt = false, int ICx = 0, int ICy = 0); + void zoom(int power, bool useCentrePt = false, int ICx = 0, int ICy = 0); + void stretchToFit(); + void setNormal(); + void getCentrePoint(int &ICx, int &ICy); + void relMoveWC(int WCdx, int WCdy); + + double WCToIC_X(double WidgetX); + double WCToIC_Y(double WidgetY); + double ICToWC_X(double ImageX); + double ICToWC_Y(double ImageY); + + void clearImage(); + int createImageCopy(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, int displayMode = IV_DISPLAY_RESET); + int pointImageTo(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, bool takeOwnership, int displayMode = IV_DISPLAY_RESET); + + void clearColorMap(); + int createColorMap(int numEntriesReq = 0, bool Initialise = true); + int getNumColorMapEntries() const { return _numMapEntries; } + int setColorMapRGBAValue(int index, float red, float green, float blue, float alpha = 1.0); + int setColorMapRedValue(int index, float value); + int setColorMapGreenValue(int index, float value); + int setColorMapBlueValue(int index, float value); + int setColorMapAlphaValue(int index, float value); + unsigned int pixValToMapIndex(double PixVal); + + void renderText(int x, int y, const QString& str, const QFont& fnt = QFont()); + +public Q_SLOTS: + void handleLoggedMessage(const QOpenGLDebugMessage &debugMessage); + +Q_SIGNALS: + void drawGraphics(); + +private: + + void initializeGL(); + void paintGL(); + void resizeGL( int w, int h ); + + void drawImage(); + void getDisplayedImageAreaSize(int &dx, int &dy); + + void getPixFormat(GLenum &pixFormat, GLenum &pixType); + void limitCurrPos(); + void limitZoomFactor(); + void setCurrPos(int x0, int y0); + void setToFit(); + void resetDisplay(); + int calcNumColorMapEntries(); + + Image::ImageBase _image; // the image data + + int _x0; // image x-coordinate of top-left widget pixel + int _y0; // image y-coordinate of top-left widget pixel + double _zoomFactor; // zoom factor = (num_widget_pixels / num_image_pixels) + + int _base_x0; // defines a fixed position of x0 + int _base_y0; // defines a fixed position of y0 + + float* _pColorMap; // a RGBA color map (to alter the intensity or colors) + int _numMapEntries; // number of entries in color map + static bool haveMesa; + +}; + + +} // namespace ImageGui + +#endif // OPENGLIMAGEBOX_H