darbyjohnston / tlRender

tlRender is an open source library for building playback and review applications for visual effects, film, and animation.
BSD 3-Clause "New" or "Revised" License
185 stars 22 forks source link

Add support for saving all layers of an OpenEXR #105

Closed ggarra13 closed 1 year ago

ggarra13 commented 1 year ago

Currently, the tlIO/*Write api saves only one layer. I modified the OpenEXRWrite.cpp code to save multipart images to support all pixel types and the fisplay, but it is limited to a single part (ie. one layer).
It would be great if we could iterate through all the layers and have the image saved with all layers, like I could do with mrViewer.

The API for sequence saving should be split into something like:

void
writeOpen(const std::string fileName, const RationalTime time, int numLayers=1)
{
  _headers.resize(numLayers);
  _outputFile = new Imf::MultiPartOutputFile(fileName, &_headers[0], numLayers);
   // fill tags
}

void
setLayerInfo(Player player, int layerId = 0)
{
   player->setLayer(layerId);
   auto info = player->getIoInfo();
   // Fill _headers[layerId] with information from info.video[0] 
   // (channels, data and display window, pixel type, compression, etc).
}

void
writeLayer(const std::shared_ptr<image::Image>& image, int layerId = 0)
{
      const uint8_t channelCount = getChannelCount(_pixelType);
      const uint8_t bitDepth = getBitDepth(_pixelType) / 8;

      Imf::OutputPart out(_outputFile, layerId);
      const Imf::Header& header = _outputFile.header(layerId);
      const Imath::Box2i& daw = header.dataWindow();

      const size_t width   = (daw.max.x - daw.min.x);
      const size_t height  = (daw.max.y - daw.min.y) + 1;
      const size_t xStride = bitDepth * channelCount;
      const size_t yStride = bitDepth * channelCount * width;

      char* base = const_cast<char*>(reinterpret_cast<const char*>(image->getData()));
      char* flip = new char[height * yStride];
      flipImageY(flip, base, height, yStride);

      Imf::FrameBuffer fb;
      auto ci = header.channels().begin();
      auto ce = header.channels().end();
      for (int k = 3; ci != ce; ++ci)
      {
          const std::string& name = ci.name();
          char* buf = flip + k-- * bitDepth;
          fb.insert(
                   name,
                   Imf::Slice(toImf(_pixelType), buf, xStride, yStride));
      }

      out.setFrameBuffer(fb);
      out.writePixels(height);
      delete flip;
}

void
writeClose()
{
  delete _outputFile;
}
darbyjohnston commented 1 year ago

I'm not sure what the use case for this would be? I have had a number of requests for converting .exr or other rendered files to movies, but not for writing out new .exr files.

ggarra13 commented 1 year ago

The main use is converting old v1 multichannel OpenEXRs to v2 multipart OpenEXRs. But I also admit it is not needed as OpenEXR now ships with the "exrmultipart" command which, I think, can do the same. I am closing this request.