diff options
Diffstat (limited to 'src/apps/ociolutimage/main.cpp')
-rw-r--r-- | src/apps/ociolutimage/main.cpp | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/apps/ociolutimage/main.cpp b/src/apps/ociolutimage/main.cpp new file mode 100644 index 0000000..2d5906b --- /dev/null +++ b/src/apps/ociolutimage/main.cpp @@ -0,0 +1,398 @@ +/* +Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. +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 Sony Pictures Imageworks 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. +*/ + +#include <OpenColorIO/OpenColorIO.h> +namespace OCIO = OCIO_NAMESPACE; +OCIO_NAMESPACE_USING; + +#include <OpenImageIO/imageio.h> +#include <OpenImageIO/typedesc.h> +#if (OIIO_VERSION < 10100) +namespace OIIO = OIIO_NAMESPACE; +#endif + +#include "argparse.h" + +#include <algorithm> +#include <cmath> +#include <fstream> +#include <string> +#include <sstream> + +#include "pystring.h" + +enum Lut3DOrder +{ + LUT3DORDER_FAST_RED = 0, + LUT3DORDER_FAST_BLUE +}; + +void WriteLut3D(const std::string & filename, const float* lutdata, int edgeLen); +void GenerateIdentityLut3D(float* img, int edgeLen, int numChannels, Lut3DOrder lut3DOrder); + + +void GetLutImageSize(int & width, int & height, + int cubesize, int maxwidth) +{ + // Compute the image width / height + width = cubesize*cubesize; + if(maxwidth>0 && width>=maxwidth) + { + // TODO: Do something smarter here to find a better multiple, + // to create a more pleasing gradient rendition. + // Use prime divisors / lowest common denominator, if possible? + width = std::min(maxwidth, width); + } + + int numpixels = cubesize*cubesize*cubesize; + height = (int)(ceilf((float)numpixels/(float)width)); +} + + +void Generate(int cubesize, int maxwidth, + const std::string & outputfile, + const std::string & configfile, + const std::string & incolorspace, + const std::string & outcolorspace) +{ + int width = 0; + int height = 0; + int numchannels = 3; + GetLutImageSize(width, height, cubesize, maxwidth); + + std::vector<float> img; + img.resize(width*height*numchannels, 0); + + GenerateIdentityLut3D(&img[0], cubesize, numchannels, LUT3DORDER_FAST_RED); + + if(!incolorspace.empty() || !outcolorspace.empty()) + { + OCIO::ConstConfigRcPtr config = OCIO::Config::Create(); + if(!configfile.empty()) + { + config = OCIO::Config::CreateFromFile(configfile.c_str()); + } + else if(getenv("OCIO")) + { + config = OCIO::Config::CreateFromEnv(); + } + else + { + std::ostringstream os; + os << "You must specify an ocio configuration "; + os << "(either with --config or $OCIO)."; + throw Exception(os.str().c_str()); + } + + OCIO::ConstProcessorRcPtr processor = + config->getProcessor(incolorspace.c_str(), outcolorspace.c_str()); + + OCIO::PackedImageDesc imgdesc(&img[0], width, height, 3); + processor->apply(imgdesc); + } + + OIIO::ImageOutput* f = OIIO::ImageOutput::create(outputfile); + if(!f) + { + throw Exception( "Could not create output image."); + } + + OIIO::ImageSpec spec(width, height, numchannels, OIIO::TypeDesc::TypeFloat); + + // TODO: If DPX, force 16-bit output? + f->open(outputfile, spec); + f->write_image(OIIO::TypeDesc::FLOAT, &img[0]); + f->close(); + delete f; +} + + +void Extract(int cubesize, int maxwidth, + const std::string & inputfile, + const std::string & outputfile) +{ + // Read the image + OIIO::ImageInput* f = OIIO::ImageInput::create(inputfile); + if(!f) + { + throw Exception("Could not create input image."); + } + + OIIO::ImageSpec spec; + f->open(inputfile, spec); + + std::string error = f->geterror(); + if(!error.empty()) + { + std::ostringstream os; + os << "Error loading image " << error; + throw Exception(os.str().c_str()); + } + + int width = 0; + int height = 0; + GetLutImageSize(width, height, cubesize, maxwidth); + + if(spec.width != width || spec.height != height) + { + std::ostringstream os; + os << "Image does not have expected dimensions. "; + os << "Expected " << width << "x" << height << ", "; + os << "Found " << spec.width << "x" << spec.height; + throw Exception(os.str().c_str()); + } + + if(spec.nchannels<3) + { + throw Exception("Image must have 3 or more channels."); + } + + int lut3DNumPixels = cubesize*cubesize*cubesize; + + if(spec.width*spec.height<lut3DNumPixels) + { + throw Exception("Image is not large enough to contain expected 3dlut."); + } + + // TODO: confirm no data window? + std::vector<float> img; + img.resize(spec.width*spec.height*spec.nchannels, 0); + f->read_image(OIIO::TypeDesc::TypeFloat, &img[0]); + delete f; + + // Repack into rgb + // Convert the RGB[...] image to an RGB image, in place. + // Of course, this only works because we're doing it from left to right + // so old pixels are read before they're written over + + if(spec.nchannels > 3) + { + for(int i=0; i<lut3DNumPixels; ++i) + { + img[3*i+0] = img[spec.nchannels*i+0]; + img[3*i+1] = img[spec.nchannels*i+1]; + img[3*i+2] = img[spec.nchannels*i+2]; + } + } + + img.resize(lut3DNumPixels*3); + + // Write the output lut + WriteLut3D(outputfile, &img[0], cubesize); +} + + + +int main (int argc, const char* argv[]) +{ + bool generate = false; + bool extract = false; + int cubesize = 32; + int maxwidth = 2048; + std::string inputfile; + std::string outputfile; + std::string config; + std::string incolorspace; + std::string outcolorspace; + + // TODO: Add optional allocation transform instead of colorconvert + ArgParse ap; + ap.options("ociolutimage -- Convert a 3dlut to or from an image\n\n" + "usage: ociolutimage [options] <OUTPUTFILE.LUT>\n\n" + "example: ociolutimage --generate --output lut.exr\n" + "example: ociolutimage --extract --input lut.exr --output output.spi3d\n", + "<SEPARATOR>", "", + "--generate", &generate, "Generate a lattice image", + "--extract", &extract, "Extract a 3dlut from an input image", + "<SEPARATOR>", "", + "--cubesize %d", &cubesize, "Size of the cube (default: 32)", + "--maxwidth %d", &maxwidth, "Specify maximum width of the image (default: 2048)", + "--input %s", &inputfile, "Specify the input filename", + "--output %s", &outputfile, "Specify the output filename", + "<SEPARATOR>", "", + "--config %s", &config, ".ocio configuration file (default: $OCIO)", + "--colorconvert %s %s", &incolorspace, &outcolorspace, "Apply a color space conversion to the image.", + NULL); + + if (ap.parse(argc, argv) < 0) + { + std::cout << ap.geterror() << std::endl; + ap.usage(); + std::cout << "\n"; + return 1; + } + + if (argc == 1 ) + { + ap.usage(); + std::cout << "\n"; + return 1; + } + + if(generate) + { + try + { + Generate(cubesize, maxwidth, + outputfile, + config, incolorspace, outcolorspace); + } + catch(std::exception & e) + { + std::cerr << "Error generating image: " << e.what() << std::endl; + exit(1); + } + catch(...) + { + std::cerr << "Error generating image. An unknown error occurred.\n"; + exit(1); + } + } + else if(extract) + { + try + { + Extract(cubesize, maxwidth, + inputfile, outputfile); + } + catch(std::exception & e) + { + std::cerr << "Error extracting lut: " << e.what() << std::endl; + exit(1); + } + catch(...) + { + std::cerr << "Error extracting lut. An unknown error occurred.\n"; + exit(1); + } + } + else + { + std::cerr << "Must specify either --generate or --extract.\n"; + exit(1); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TODO: These should be exposed from inside OCIO, in appropriate time. +// + +inline int GetLut3DIndex_B(int indexR, int indexG, int indexB, + int sizeR, int sizeG, int /*sizeB*/) +{ + return 3 * (indexR + sizeR * (indexG + sizeG * indexB)); +} + +inline int GetLut3DIndex_R(int indexR, int indexG, int indexB, + int /*sizeR*/, int sizeG, int sizeB) +{ + return 3 * (indexB + sizeB * (indexG + sizeG * indexR)); +} + +void GenerateIdentityLut3D(float* img, int edgeLen, int numChannels, + Lut3DOrder lut3DOrder) +{ + if(!img) return; + if(numChannels < 3) + { + throw Exception("Cannot generate idenitity 3d lut with less than 3 channels."); + } + + float c = 1.0f / ((float)edgeLen - 1.0f); + + if(lut3DOrder == LUT3DORDER_FAST_RED) + { + for(int i=0; i<edgeLen*edgeLen*edgeLen; i++) + { + img[numChannels*i+0] = (float)(i%edgeLen) * c; + img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c; + img[numChannels*i+2] = (float)((i/edgeLen/edgeLen)%edgeLen) * c; + } + } + else if(lut3DOrder == LUT3DORDER_FAST_BLUE) + { + for(int i=0; i<edgeLen*edgeLen*edgeLen; i++) + { + img[numChannels*i+0] = (float)((i/edgeLen/edgeLen)%edgeLen) * c; + img[numChannels*i+1] = (float)((i/edgeLen)%edgeLen) * c; + img[numChannels*i+2] = (float)(i%edgeLen) * c; + } + } + else + { + throw Exception("Unknown Lut3DOrder."); + } +} + +void WriteLut3D(const std::string & filename, const float* lutdata, int edgeLen) +{ + if(!pystring::endswith(filename,".spi3d")) + { + std::ostringstream os; + os << "Only .spi3d writing is currently supported. "; + os << "As a work around, please write a .spi3d file, and then use "; + os << "ociobakelut for transcoding."; + throw Exception(os.str().c_str()); + } + + std::ofstream output; + output.open(filename.c_str()); + if(!output.is_open()) + { + std::ostringstream os; + os << "Error opening " << filename << " for writing."; + throw Exception(os.str().c_str()); + } + + output << "SPILUT 1.0\n"; + output << "3 3\n"; + output << edgeLen << " " << edgeLen << " " << edgeLen << "\n"; + + int index = 0; + for(int rindex=0; rindex<edgeLen; ++rindex) + { + for(int gindex=0; gindex<edgeLen; ++gindex) + { + for(int bindex=0; bindex<edgeLen; ++bindex) + { + index = GetLut3DIndex_B(rindex, gindex, bindex, + edgeLen, edgeLen, edgeLen); + + output << rindex << " " << gindex << " " << bindex << " "; + output << lutdata[index+0] << " "; + output << lutdata[index+1] << " "; + output << lutdata[index+2] << "\n"; + } + } + } + + output.close(); +} |