Skip to content

Commit

Permalink
feat(iv): OCIO color managed display (AcademySoftwareFoundation#4031)
Browse files Browse the repository at this point in the history
This change adds OCIO functionality to iv.

To enable OCIO, first set the $OCIO environment variable to point with a
path to the OCIO config. "ocio://default" should work as well.

If a valid config provided, a new menu gets added under "View/OCIO" with
an on/off toggle and submenus to select the image color space and
display/view and optional look.

If an image color space, display and view are provided as command line
parameters ("image-color-space", "display", "view"), then the OCIO mode
is switched on automatically.

If no image color space is provided as command line parameters, the top
most color space of the config get pre-selected in the menus.

If no display or view is provided as command line parameters, the
default values get pre-selected in the menus.

In OCIO mode the exposure and gamma adjustments are done by OCIO. The
exposure is done in scene linear, gamma is still in display space. The
exposure adjustments don't match between the OCIO and non-OCIO modes.

When OCIO fails to initialise the pipeline for any reason, an error
message is shown in the status bar, and the non-OCIO path gets used.

TODO:
- All channel shuffling is done by the existing shader and happens
before the OCIO step. We may want to discuss/change that?
- The initial image color space gets selected from the command line
parameter, or the first color space in the config. There is no attempt
being made to use the image metadata.
- The exposure/gamma values get baked into the shader, which gets
recompiled on every change. We may want to use uniforms instead. - DONE


---------

Signed-off-by: Anton Dukhovnikov <[email protected]>
  • Loading branch information
antond-weta authored Dec 19, 2023
1 parent a685910 commit 5fe4441
Show file tree
Hide file tree
Showing 8 changed files with 944 additions and 115 deletions.
5 changes: 5 additions & 0 deletions src/iv/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ if (iv_enabled AND (Qt5_FOUND OR Qt6_FOUND) AND OPENGL_FOUND)
$<TARGET_NAME_IF_EXISTS:Qt6::Widgets>
$<TARGET_NAME_IF_EXISTS:Qt6::OpenGLWidgets>
${OPENGL_LIBRARIES}
$<TARGET_NAME_IF_EXISTS:OpenColorIO::OpenColorIO>
$<TARGET_NAME_IF_EXISTS:OpenColorIO::OpenColorIOHeaders>
)
if (iv_enabled AND FORCE_OPENGL_1)
target_compile_definitions(iv PRIVATE FORCE_OPENGL_1)
Expand All @@ -33,6 +35,9 @@ if (iv_enabled AND (Qt5_FOUND OR Qt6_FOUND) AND OPENGL_FOUND)
if (Qt6_FOUND)
target_compile_definitions(iv PRIVATE OIIO_QT_MAJOR=6)
endif ()
if (APPLE)
target_compile_definitions(iv PRIVATE GL_SILENCE_DEPRECATION)
endif ()
else ()
message (STATUS "\n\n WARNING: Qt or OpenGL not found -- 'iv' will not be built!\n")
endif ()
177 changes: 174 additions & 3 deletions src/iv/imageviewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "imageviewer.h"
#include "ivgl.h"
#include "ivgl_ocio.h"

#include <QApplication>
#include <QComboBox>
Expand All @@ -35,13 +36,15 @@
# include <QDesktopWidget>
#endif

#include <OpenImageIO/color.h>
#include <OpenImageIO/dassert.h>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/imagecache.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/sysutil.h>
#include <OpenImageIO/timer.h>


#include "ivutils.h"


Expand Down Expand Up @@ -93,7 +96,8 @@ static const char *s_file_filters = ""



ImageViewer::ImageViewer()
ImageViewer::ImageViewer(bool use_ocio, const std::string& image_color_space,
const std::string& display, const std::string& view)
: infoWindow(NULL)
, preferenceWindow(NULL)
, darkPaletteBox(NULL)
Expand All @@ -105,6 +109,12 @@ ImageViewer::ImageViewer()
, m_fullscreen(false)
, m_default_gamma(1)
, m_darkPalette(false)
#ifdef HAS_OCIO_2
, m_useOCIO(use_ocio)
, m_ocioColourSpace(image_color_space)
, m_ocioDisplay(display)
, m_ocioView(view)
#endif // HAS_OCIO_2
{
readSettings(false);

Expand All @@ -126,7 +136,13 @@ ImageViewer::ImageViewer()
slideTimer = new QTimer();
slideDuration_ms = 5000;
slide_loop = true;
glwin = new IvGL(this, *this);

#ifdef HAS_OCIO_2
glwin = new IvGL_OCIO(this, *this);
#else
glwin = new IvGL(this, *this);
#endif

glwin->setPalette(m_palette);
glwin->resize(m_default_width, m_default_height);
setCentralWidget(glwin);
Expand Down Expand Up @@ -444,7 +460,151 @@ ImageViewer::createActions()
SLOT(setSlideShowDuration(int)));
}

#ifdef HAS_OCIO_2

void
ImageViewer::createOCIOMenus(QMenu* parent)
{
ocioColorSpacesMenu = new QMenu(tr("Image color space"));
ocioDisplaysMenu = new QMenu(tr("Display/View"));

try {
ColorConfig config;

std::map<std::string, QMenu*> colourSpaceFamilies;

ocioColorSpacesGroup = new QActionGroup(ocioColorSpacesMenu);
ocioColorSpacesGroup->setExclusive(true);

for (int i = 0; i < config.getNumColorSpaces(); i++) {
const char* colorSpaceName = config.getColorSpaceNameByIndex(i);

if (colorSpaceName && *colorSpaceName) {
// If no color space provided via command line parameters, select the top color space in the list.
if (m_ocioColourSpace == "" && i == 0) {
m_ocioColourSpace = colorSpaceName;
}

const char* family = config.getColorSpaceFamilyByName(
colorSpaceName);

QMenu* targetMenu;
if (family && *family) {
auto iter = colourSpaceFamilies.find(family);
if (iter == colourSpaceFamilies.end()) {
targetMenu = new QMenu(family);
ocioColorSpacesMenu->addMenu(targetMenu);
colourSpaceFamilies[family] = targetMenu;
} else {
targetMenu = iter->second;
}
} else {
targetMenu = ocioColorSpacesMenu;
}

QAction* action = new QAction(colorSpaceName, this);
action->setCheckable(true);
action->setChecked(m_ocioColourSpace == colorSpaceName);

connect(action, SIGNAL(triggered()), this,
SLOT(ocioColorSpaceAction()));

ocioColorSpacesGroup->addAction(action);
targetMenu->addAction(action);
}
}

ocioDisplayViewsGroup = new QActionGroup(ocioDisplaysMenu);
ocioDisplayViewsGroup->setExclusive(true);

if (m_ocioDisplay == "" || m_ocioDisplay == "default") {
m_ocioDisplay = config.getDefaultDisplayName();
}

if (m_ocioView == "" || m_ocioView == "default") {
m_ocioView = config.getDefaultViewName();
}

for (int i = 0; i < config.getNumDisplays(); i++) {
const char* display = config.getDisplayNameByIndex(i);

if (display && *display) {
QMenu* menu = new QMenu(display);

for (int j = 0; j < config.getNumViews(display); j++) {
const char* view = config.getViewNameByIndex(display, j);

if (view && *view) {
QAction* action = new QAction(view, menu);
action->setCheckable(true);
action->setChecked(m_ocioDisplay == display
&& m_ocioView == view);

connect(action, SIGNAL(triggered()), this,
SLOT(ocioDisplayViewAction()));

menu->addAction(action);
ocioDisplayViewsGroup->addAction(action);
}
}

ocioDisplaysMenu->addMenu(menu);
}
}
} catch (...) {
std::cerr << "Error loading OCIO config file" << std::endl;
m_useOCIO = false;
return;
}

QMenu* ocioMenu = new QMenu(tr("OCIO"));

QAction* action = new QAction(tr("Use OCIO"));
action->setCheckable(true);
action->setChecked(m_useOCIO);
connect(action, SIGNAL(toggled(bool)), this, SLOT(useOCIOAction(bool)));

ocioMenu->addAction(action);
ocioMenu->addMenu(ocioColorSpacesMenu);
ocioMenu->addMenu(ocioDisplaysMenu);

parent->addMenu(ocioMenu);
}

void
ImageViewer::useOCIOAction(bool checked)
{
m_useOCIO = checked;

ocioColorSpacesMenu->setEnabled(m_useOCIO);
ocioDisplaysMenu->setEnabled(m_useOCIO);

displayCurrentImage();
}

void
ImageViewer::ocioColorSpaceAction()
{
QAction* action = ocioColorSpacesGroup->checkedAction();
if (action) {
m_ocioColourSpace = action->text().toStdString();
displayCurrentImage();
}
}

void
ImageViewer::ocioDisplayViewAction()
{
QAction* action = ocioDisplayViewsGroup->checkedAction();
if (action) {
QMenu* menu = qobject_cast<QMenu*>(action->parent());
m_ocioDisplay = menu->title().toStdString();
m_ocioView = action->text().toStdString();
displayCurrentImage();
}
}

#endif // HAS_OCIO_2

void
ImageViewer::createMenus()
Expand Down Expand Up @@ -531,6 +691,11 @@ ImageViewer::createMenus()
viewMenu->addAction(viewSubimageNextAct);
viewMenu->addMenu(channelMenu);
viewMenu->addMenu(colormodeMenu);

#ifdef HAS_OCIO_2
createOCIOMenus(viewMenu);
#endif

viewMenu->addMenu(expgamMenu);
menuBar()->addMenu(viewMenu);
// Full screen mode
Expand Down Expand Up @@ -877,7 +1042,13 @@ void
ImageViewer::moveToNewWindow()
{
if (m_images.size()) {
ImageViewer* imageViewer = new ImageViewer();
#ifdef HAS_OCIO_2
ImageViewer* imageViewer = new ImageViewer(m_useOCIO, m_ocioColourSpace,
m_ocioDisplay, m_ocioView);
#else
std::string dummy;
ImageViewer* imageViewer = new ImageViewer(false, dummy, dummy, dummy);
#endif

imageViewer->show();
imageViewer->rawcolor(rawcolor());
Expand Down
40 changes: 39 additions & 1 deletion src/iv/imageviewer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <vector>

#include <QAction>
#include <QActionGroup>
#include <QCheckBox>
#include <QDialog>
#include <QMainWindow>
Expand All @@ -38,6 +39,8 @@
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imageio.h>

#include "ivgl_ocio.h"

using namespace OIIO;

class QComboBox;
Expand Down Expand Up @@ -143,7 +146,8 @@ class ImageViewer final : public QMainWindow {
Q_OBJECT

public:
ImageViewer();
ImageViewer(bool use_ocio, const std::string& image_color_space,
const std::string& display, const std::string& view);
~ImageViewer();

enum COLOR_MODE {
Expand Down Expand Up @@ -240,6 +244,13 @@ class ImageViewer final : public QMainWindow {
void rawcolor(bool val) { m_rawcolor = val; }
bool rawcolor() const { return m_rawcolor; }

#ifdef HAS_OCIO_2
bool useOCIO() { return m_useOCIO; }
const std::string& ocioColorSpace() { return m_ocioColourSpace; }
const std::string& ocioDisplay() { return m_ocioDisplay; }
const std::string& ocioView() { return m_ocioView; }
#endif // HAS_OCIO_2

private slots:
void open(); ///< Dialog to open new image from file
void reload(); ///< Reread current image from disk
Expand Down Expand Up @@ -298,6 +309,13 @@ private slots:
void showInfoWindow(); ///< View extended info on image
void showPixelviewWindow(); ///< View closeup pixel view
void editPreferences(); ///< Edit viewer preferences

#ifdef HAS_OCIO_2
void useOCIOAction(bool checked);
void ocioColorSpaceAction();
void ocioDisplayViewAction();
#endif // HAS_OCIO_2

private:
void createActions();
void createMenus();
Expand Down Expand Up @@ -329,6 +347,7 @@ private slots:
// QPrinter printer;
#endif


QAction *openAct, *reloadAct, *closeImgAct;
static const unsigned int MaxRecentFiles = 10;
QAction* openRecentAct[MaxRecentFiles];
Expand Down Expand Up @@ -407,6 +426,25 @@ private slots:
friend class IvInfoWindow;
friend class IvPreferenceWindow;
friend bool image_progress_callback(void* opaque, float done);

#ifdef HAS_OCIO_2
friend class IvGL_OCIO;

void createOCIOMenus(QMenu* parent);

QMenu* ocioColorSpacesMenu;
QMenu* ocioDisplaysMenu;
QMenu* ocioOptimizationMenu;

QActionGroup* ocioColorSpacesGroup;
QActionGroup* ocioDisplayViewsGroup;
QActionGroup* ocioOptimizationGroup;

bool m_useOCIO;
std::string m_ocioColourSpace;
std::string m_ocioDisplay;
std::string m_ocioView;
#endif // HAS_OCIO_2
};


Expand Down
Loading

0 comments on commit 5fe4441

Please sign in to comment.