/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012, Industrial Light & Magic, a division of Lucas // Digital Ltd. LLC // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Industrial Light & Magic nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- // // exrdisplay -- a simple program to display Imf::Rgba multipart // and deep images // -- exrdisplay Window Mouse Control: // LMB = Display a sample chart and print out values // RMB = If it's a deep image, open a Deep 3D Window // -- Deep 3D Window Mouse Control: // LMB = Rotate // RMB = Zoom // MMB = Move // -- Deep 3D Window Control Keys: // a = scale z value up // s = scale z value down // f = reset to fit // d = decrease pixel samples // c = increase pixel samples // //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "loadImage.h" #include "scaleImage.h" #include "applyCtl.h" #include "ImageView.h" #include "namespaceAlias.h" using namespace IMF; using namespace IMATH; using namespace std; struct MainWindow { Fl_Window * window; Fl_Choice * multipartMenu; Fl_Output * typeLabel; Fl_Output * nameLabel; Fl_Box * exposureLabel; Fl_Value_Slider * exposureSlider; Fl_Box * defogLabel; Fl_Value_Slider * defogSlider; Fl_Box * kneeLowLabel; Fl_Value_Slider * kneeLowSlider; Fl_Box * kneeHighLabel; Fl_Value_Slider * kneeHighSlider; Fl_Box * rgbaBox; ImageView * image; Array pixels; Array dataZ; Array sampleCount; const char* imageFile; bool preview; int lx; int ly; const char* channel; const char* layer; bool swap; float farPlane; bool deepComp; static void multipartComboboxCallback (Fl_Widget *widget, void *data); static void exposureSliderCallback (Fl_Widget *widget, void *data); static void defogSliderCallback (Fl_Widget *widget, void *data); static void kneeLowSliderCallback (Fl_Widget *widget, void *data); static void kneeHighSliderCallback (Fl_Widget *widget, void *data); }; void MainWindow::multipartComboboxCallback (Fl_Widget *widget, void *data) { MainWindow *mainWindow = (MainWindow *) data; int partnum = mainWindow->multipartMenu->value(); mainWindow->image->clearDataDisplay(); // // reload pixels // Header header; int zsize; loadImage (mainWindow->imageFile, mainWindow->channel, mainWindow->layer, mainWindow->preview, mainWindow->lx, mainWindow->ly, partnum, zsize, header, mainWindow->pixels, mainWindow->dataZ, mainWindow->sampleCount, mainWindow->deepComp); const Box2i &displayWindow = header.displayWindow(); const Box2i &dataWindow = header.dataWindow(); int dw = dataWindow.max.x - dataWindow.min.x + 1; int dh = dataWindow.max.y - dataWindow.min.y + 1; int dx = dataWindow.min.x - displayWindow.min.x; int dy = dataWindow.min.y - displayWindow.min.y; if (mainWindow->swap) swapPixels (dw, dh, mainWindow->pixels); if(mainWindow->preview) { int w = mainWindow->window->w(); int iw = displayWindow.max.x - displayWindow.min.x; int ih = displayWindow.max.y - displayWindow.min.y; mainWindow->window->size (w, (160 + ih)); mainWindow->image->resize ((w - iw) / 2, 155, iw, ih); } mainWindow->image->setPixels (mainWindow->pixels, mainWindow->dataZ, mainWindow->sampleCount, zsize, dw,dh,dx,dy); // // renew multipart data type // string type = ""; try{ type = header.type(); } catch(IEX_NAMESPACE::BaseExc &e) { type = ""; } mainWindow->typeLabel->value(type.c_str()); // // renew multipart part name // string name = ""; try{ name = header.name(); } catch(IEX_NAMESPACE::BaseExc &e) { name = ""; } mainWindow->nameLabel->value(name.c_str()); } void MainWindow::exposureSliderCallback (Fl_Widget *widget, void *data) { MainWindow *mainWindow = (MainWindow *) data; mainWindow->image->setExposure (mainWindow->exposureSlider->value()); } void MainWindow::defogSliderCallback (Fl_Widget *widget, void *data) { MainWindow *mainWindow = (MainWindow *) data; mainWindow->image->setDefog (mainWindow->defogSlider->value()); } void MainWindow::kneeLowSliderCallback (Fl_Widget *widget, void *data) { MainWindow *mainWindow = (MainWindow *) data; mainWindow->image->setKneeLow (mainWindow->kneeLowSlider->value()); } void MainWindow::kneeHighSliderCallback (Fl_Widget *widget, void *data) { MainWindow *mainWindow = (MainWindow *) data; mainWindow->image->setKneeHigh (mainWindow->kneeHighSlider->value()); } MainWindow * makeMainWindow (const char imageFile[], const char channel[], const char layer[], bool preview, int lx, int ly, float farPlane, bool noDisplayWindow, bool noAspect, bool zeroOneExposure, bool normalize, bool swap, bool continuousUpdate, const vector &transformNames, bool useCtl, bool deepComp) { MainWindow *mainWindow = new MainWindow; mainWindow->imageFile = imageFile; mainWindow->preview = preview; mainWindow->lx = lx; mainWindow->ly = ly; mainWindow->farPlane = farPlane; mainWindow->channel = channel; mainWindow->layer = layer; mainWindow->swap = swap; mainWindow->deepComp = deepComp; // // Read the image file. // int numparts = 0; try { MultiPartInputFile *infile = new MultiPartInputFile(imageFile); numparts = infile->parts(); delete infile; } catch(IEX_NAMESPACE::BaseExc &e) { cerr<<"\n"<<"ERROR:"<pixels, mainWindow->dataZ, mainWindow->sampleCount, mainWindow->deepComp); const Box2i &displayWindow = header.displayWindow(); const Box2i &dataWindow = header.dataWindow(); float pixelAspectRatio = header.pixelAspectRatio(); int w = displayWindow.max.x - displayWindow.min.x + 1; int h = displayWindow.max.y - displayWindow.min.y + 1; int dw = dataWindow.max.x - dataWindow.min.x + 1; int dh = dataWindow.max.y - dataWindow.min.y + 1; int dx = dataWindow.min.x - displayWindow.min.x; int dy = dataWindow.min.y - displayWindow.min.y; if (noDisplayWindow) { w = dw; h = dh; dx = 0; dy = 0; } if (noAspect) { pixelAspectRatio = 1; } // // Normalize the pixel data if necessary. // if (normalize) normalizePixels (dw, dh, mainWindow->pixels); // // If necessary, swap the top and bottom half and then the // left and right half of the image. // if (swap) swapPixels (dw, dh, mainWindow->pixels); // // Stretch the image horizontally or vertically to make the // pixels square (assuming that we are going to display the // image on a screen with square pixels). // if (pixelAspectRatio > 1) scaleX (pixelAspectRatio, w, h, dw, dh, dx, dy, mainWindow->pixels); else scaleY (1 / pixelAspectRatio, w, h, dw, dh, dx, dy, mainWindow->pixels); // // Apply CTL transforms if requested. // // If we don't apply CTL transforms and we have loaded more than // one image channel, then transform the pixels from the RGB space // of the input file to into the RGB space of the display. // #if HAVE_CTL_INTERPRETER if (useCtl) { applyCtl (transformNames, header, mainWindow->pixels, dw, dh, mainWindow->pixels); } else #endif if (!channel) { adjustChromaticities (header, mainWindow->pixels, dw, dh, mainWindow->pixels); } // // Build main window // int mw = max (500, w); // main window width int vy = 0; // offset of image view from top of main window float exposure = 0; float defog = 0; float kneeLow = 0; float kneeHigh = 0; #if HAVE_CTL_INTERPRETER if (useCtl) { // // Colors on the screeen are computed by CTL. // No exposure defog and knee sliders are displayed. // mainWindow->window = new Fl_Window (mw + 10, h + 35, imageFile); // // Add RGB value display // mainWindow->rgbaBox = new Fl_Box (80, 5, mw - 65, 20, ""); mainWindow->rgbaBox->align (FL_ALIGN_LEFT | FL_ALIGN_INSIDE); // // Image view is below RGB value display // vy = 30; // // Map floating-point pixel values 0.0 and 1.0 // to the display's white and black respectively. // exposure = 1.02607; defog = 0; kneeLow = 0.0; kneeHigh = 3.5; } else #endif { mainWindow->window = new Fl_Window (mw + 10, h + 160, imageFile); // // Add multipart combobox // mainWindow->multipartMenu = new Fl_Choice (70, 5, 80, 20, "multipart"); for(int i=0; imultipartMenu->add(s.str().c_str()); } mainWindow->multipartMenu->value(0); mainWindow->multipartMenu->callback (MainWindow::multipartComboboxCallback, mainWindow); if(numparts==1) mainWindow->multipartMenu->deactivate(); else{ // // Add type label // string type = ""; try{ type = header.type(); } catch(IEX_NAMESPACE::BaseExc &e) { type = ""; } mainWindow->typeLabel = new Fl_Output (190, 5, 110, 20, "type"); mainWindow->typeLabel->value(type.c_str()); // // Add name label // string name = ""; try{ name = header.name(); } catch(IEX_NAMESPACE::BaseExc &e) { name = ""; } mainWindow->nameLabel = new Fl_Output (350, 5, 155, 20, "name"); mainWindow->nameLabel->value(name.c_str()); } // // Add exposure slider // mainWindow->exposureLabel = new Fl_Box (5, 30, 60, 20, "exposure"); mainWindow->exposureSlider = new Fl_Value_Slider (70, 30, mw - 65, 20, ""); enum Fl_When when = continuousUpdate? FL_WHEN_CHANGED : FL_WHEN_RELEASE; mainWindow->exposureSlider->type (FL_HORIZONTAL); mainWindow->exposureSlider->range (-10.0, +10.0); mainWindow->exposureSlider->step (1, 8); exposure = zeroOneExposure? 1.02607: 0.0; mainWindow->exposureSlider->value (exposure); mainWindow->exposureSlider->when (when); mainWindow->exposureSlider->callback (MainWindow::exposureSliderCallback, mainWindow); // // Add defog slider // mainWindow->defogLabel = new Fl_Box (5, 55, 60, 20, "defog"); mainWindow->defogSlider = new Fl_Value_Slider (70, 55, mw - 65, 20, ""); mainWindow->defogSlider->type (FL_HORIZONTAL); mainWindow->defogSlider->range (0.0, 0.01); mainWindow->defogSlider->step (1, 10000); defog = 0.0; mainWindow->defogSlider->value (defog); mainWindow->defogSlider->when (when); mainWindow->defogSlider->callback (MainWindow::defogSliderCallback, mainWindow); // // Add kneeLow slider // mainWindow->kneeLowLabel = new Fl_Box (5, 80, 60, 20, "knee low"); mainWindow->kneeLowSlider = new Fl_Value_Slider (70, 80, mw - 65, 20, ""); mainWindow->kneeLowSlider->type (FL_HORIZONTAL); mainWindow->kneeLowSlider->range (-3.0, 3.0); mainWindow->kneeLowSlider->step (1, 8); kneeLow = 0.0; mainWindow->kneeLowSlider->value (kneeLow); mainWindow->kneeLowSlider->when (when); mainWindow->kneeLowSlider->callback (MainWindow::kneeLowSliderCallback, mainWindow); // // Add kneeHigh slider // mainWindow->kneeHighLabel = new Fl_Box (5, 105, 60, 20, "knee high"); mainWindow->kneeHighSlider = new Fl_Value_Slider (70, 105, mw - 65, 20, ""); mainWindow->kneeHighSlider->type (FL_HORIZONTAL); mainWindow->kneeHighSlider->range (3.5, 7.5); mainWindow->kneeHighSlider->step (1, 8); kneeHigh = (preview | zeroOneExposure)? 3.5: 5.0; mainWindow->kneeHighSlider->value (kneeHigh); mainWindow->kneeHighSlider->when (when); mainWindow->kneeHighSlider->callback (MainWindow::kneeHighSliderCallback, mainWindow); // // Add RGB value display // mainWindow->rgbaBox = new Fl_Box (80, 130, mw - 65, 20, ""); mainWindow->rgbaBox->align (FL_ALIGN_LEFT | FL_ALIGN_INSIDE); // // Image view is below RGB value display // vy = 155; } // // Add image view: // // w, h width and height of the display window // // dw, dh width and height of the data window // // dx, dy offset of the data window's upper left // corner from the display window's upper // left corner // mainWindow->image = new ImageView (5 + (mw - w) / 2, vy, w, h, "", mainWindow->pixels, mainWindow->dataZ, mainWindow->sampleCount, zsize, dw, dh, dx, dy, mainWindow->rgbaBox, mainWindow->farPlane, displayVideoGamma(), exposure, defog, kneeLow, kneeHigh); mainWindow->image->box (FL_ENGRAVED_BOX); mainWindow->window->end(); return mainWindow; } void usageMessage (const char argv0[], bool verbose = false) { cerr << "usage: " << argv0 << " [options] imagefile" << endl; if (verbose) { cerr << "\n" "Displays an OpenEXR image on the screen.\n" "\n" "Options:\n" "\n" "-p displays the preview (thumbnail)\n" " image instead of the main image\n" "\n" "-L x displays layer x of a multilayer image\n" "\n" "-l lx ly displays level (lx,ly) of a tiled\n" " multiresolution image\n" "\n" "-w displays all pixels in the data window,\n" " ignoring the display window\n" "\n" "-a ignores the image's pixel aspect ratio,\n" " and does not scale the image to make\n" " the pixels square\n" "\n" "-c x loads only image channel x\n" "\n" "-1 sets exposure and knee sliders so that pixel\n" " value 0.0 becomes black, and 1.0 becomes white\n" "\n" "-n normalizes the pixels so that the smallest\n" " value becomes 0.0 and the largest value\n" " becomes 1.0\n" "\n" "-A same as -c A -1 (displays alpha)\n" "\n" "-Z same as -c Z -n (displays depth)\n" "\n" "-s swaps the image's top and bottom half, then\n" " swaps the left and right half, so that the\n" " four corners of the image end up in the center.\n" " (Useful for checking the seams of wrap-around\n" " texture map images.)\n" #if HAVE_CTL_INTERPRETER "\n" "-C s CTL transform s is applied to the image before\n" " it is displayed. Option -C can be specified\n" " multiple times to apply a series of transforms\n" " to the image. The transforms are applied in the\n" " order in which they appear on the command line.\n" "\n" "-T do not apply CTL transforms to the image; enable\n" " interactive exposure and knee controls instead\n" "\n" "-u changing the exposure and knee controls\n" " continuously updates the on-screen image\n" " (the controls are enabled only when no CTL\n" " transforms have been applied to the image)\n" "\n" "-t n use n parallel threads to read the image file\n" " and to run the CTL transforms\n" #else "\n" "-u changing the exposure and knee controls\n" " continuously updates the on-screen image\n" "\n" "-t n use n parallel threads to read the image file\n" #endif "\n" "-h prints this message\n" #if HAVE_CTL_INTERPRETER "\n" "CTL transforms:\n" "\n" " CTL transforms are applied to the image unless\n" " one of the following options is specified on the\n" " command line: -c, -1, -n, -A, -Z, -T\n" "\n" " If one or more CTL transforms are specified on\n" " the command line (using the -C flag), then those\n" " transforms are applied to the image.\n" " If no CTL transforms are specified on the command\n" " line then an optional look modification transform\n" " is applied, followed by a rendering transform and\n" " a display transform.\n" " The name of the look modication transform is taken\n" " from the lookModTransform attribute in the header\n" " of the image file. If the header contains no such\n" " attribute, then no look modication transform is\n" " applied. The name of the rendering transform is\n" " taken from the renderingTransform attribute in the\n" " header of the image file. If the header contains\n" " no such attribute, then the name of the rendering\n" " transform is \"transform_RRT.\" The name of the\n" " display transform is taken from the environment\n" " variable CTL_DISPLAY_TRANSFORM. If this environment\n" " variable is not set, then the name of the display\n" " transform is \"transform_display_video.\"\n" " The files that contain the CTL code for the\n" " transforms are located using the CTL_MODULE_PATH\n" " environment variable.\n" #endif "\n" "Deep Data Options:\n" "\n" "-farPlane(f) f OpenGL zFar clipping plane\n" "-noDeepComp(d) Disable the compositing of deep images (def=ON)\n" "\n" "Exrdisplay Window Mouse Control:\n" " LMB = Display a sample chart and print out values\n" " RMB = If it's a deep image, open a Deep 3D Window\n" "Deep 3D Window Mouse Control:\n" " LMB = Rotate\n" " RMB = Zoom\n" " MMB = Move\n" "Deep 3D Window Control Keys:\n" " a = scale z value up\n" " s = scale z value down\n" " f = reset to fit\n" " d = decrease pixel samples\n" " c = increase pixel samples\n" "\n"; cerr << endl; } exit (1); } void window_callback(Fl_Widget*, void*) { exit(0); } int main(int argc, char **argv) { const char *imageFile = 0; const char *channel = 0; const char *layer = 0; bool preview = false; bool noDisplayWindow = false; bool noAspect = false; bool zeroOneExposure = false; bool normalize = false; bool swap = false; bool continuousUpdate = false; vector transformNames; bool useCtl = true; int numThreads = 0; int lx = -1; int ly = -1; float farPlane = limits::max(); //default value of zfar plane bool deepComp = true; // attempt to composite deep images together // // Parse the command line. // if (argc < 2) usageMessage (argv[0], true); int i = 1; while (i < argc) { if (!strcmp (argv[i], "-p")) { // // Show the preview image // preview = true; i += 1; } else if (!strcmp (argv[i], "-L")) { // // Assume that the image file has multiple // layers, and show the specified layer. // if (i > argc - 2) usageMessage (argv[0]); layer = argv[i + 1]; i += 2; } else if (!strcmp (argv[i], "-l")) { // // Assume that the image file is tiled, // and show level (lx,ly) of the tiled image // if (i > argc - 3) usageMessage (argv[0]); lx = strtol (argv[i + 1], 0, 0); ly = strtol (argv[i + 2], 0, 0); i += 3; } else if (!strcmp (argv[i], "-w")) { // // Ignore the file's display window // noDisplayWindow = true; i += 1; } else if (!strcmp (argv[i], "-a")) { // // Ignore the file's pixel aspect ratio // noAspect = true; i += 1; } else if (!strcmp (argv[i], "-c")) { // // Load only one image channel. // if (i > argc - 2) usageMessage (argv[0]); channel = argv[i + 1]; useCtl = false; i += 2; } else if (!strcmp (argv[i], "-1")) { // // Display 0.0 to 1.0 range. // zeroOneExposure = true; useCtl = false; i += 1; } else if (!strcmp (argv[i], "-n")) { // // Normalize pixels. // zeroOneExposure = true; normalize = true; useCtl = false; i += 1; } else if (!strcmp (argv[i], "-A")) { // // Display alpha // zeroOneExposure = true; normalize = false; channel = "A"; useCtl = false; i += 1; } else if (!strcmp (argv[i], "-Z")) { // // Display depth // zeroOneExposure = true; normalize = true; channel = "Z"; useCtl = false; i += 1; } else if (!strcmp (argv[i], "-s")) { // // Swap top and bottom half, then left and right half. // swap = true; i += 1; } else if (!strcmp (argv[i], "-u")) { // // Continuous update. // continuousUpdate = true; i += 1; } else if (!strcmp (argv[i], "-C")) { // // Apply a CTL transform // if (i > argc - 2) usageMessage (argv[0]); transformNames.push_back (argv[i + 1]); i += 2; } else if (!strcmp (argv[i], "-T")) { // // No CTL transforms. // useCtl = false; i += 1; } else if (!strcmp (argv[i], "-t")) { // // Set number of threads // if (i > argc - 2) usageMessage (argv[0]); numThreads = strtol (argv[i + 1], 0, 0); if (numThreads < 0) { cerr << "Number of threads cannot be negative." << endl; return 1; } i += 2; } else if (!strcmp (argv[i], "-h")) { // // Print help message // usageMessage (argv[0], true); } else if (!strcmp (argv[i], "-farPlane") || !strcmp (argv[i], "-f")) { // // zFar plane for display deep data // if (i > argc - 2) usageMessage (argv[0]); farPlane = strtod (argv[i + 1], 0); i += 2; if (farPlane <= 0) { cerr << "Value of far Plane cannot be negative." << endl; exit (1); } } else if (!strcmp (argv[i], "-noDeepComp") || !strcmp (argv[i], "-d")) { // // Toogle the option to composite deep images, else just use // the first sample of each pixel // deepComp = false; i += 1; } else { // // image file name // imageFile = argv[i]; i += 1; } } if (imageFile == 0) usageMessage (argv[0]); // // Load the specified image file, // open a window on the screen, and // display the image. // int exitStatus = 0; try { setGlobalThreadCount (numThreads); MainWindow *mainWindow = makeMainWindow (imageFile, channel, layer, preview, lx, ly, farPlane, noDisplayWindow, noAspect, zeroOneExposure, normalize, swap, continuousUpdate, transformNames, useCtl, deepComp); mainWindow->window->show (1, argv); Fl::set_color (FL_GRAY, 240, 240, 240); Fl::set_color (FL_GRAY0, 80, 80, 80); mainWindow->window->callback(window_callback); // set main window exit exitStatus = Fl::run(); } catch (const exception &e) { cerr << e.what() << endl; exitStatus = 1; } return exitStatus; }