//
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) Contributors to the OpenEXR Project.
//

//-----------------------------------------------------------------------------
//
//	class TiledRgbaOutputFile
//	class TiledRgbaInputFile
//
//-----------------------------------------------------------------------------

#include <ImfArray.h>
#include <ImfChannelList.h>
#include <ImfRgbaFile.h>
#include <ImfRgbaYca.h>
#include <ImfStandardAttributes.h>
#include <ImfTileDescriptionAttribute.h>
#include <ImfTiledInputFile.h>
#include <ImfTiledOutputFile.h>
#include <ImfTiledRgbaFile.h>

#include "Iex.h"
#include "ImfNamespace.h"

#if ILMTHREAD_THREADING_ENABLED
#    include <mutex>
#endif

OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER

using namespace std;
using namespace IMATH_NAMESPACE;
using namespace RgbaYca;

namespace
{

void
insertChannels (
    Header& header, RgbaChannels rgbaChannels, const char fileName[])
{
    ChannelList ch;

    if (rgbaChannels & (WRITE_Y | WRITE_C))
    {
        if (rgbaChannels & WRITE_Y) { ch.insert ("Y", Channel (HALF, 1, 1)); }

        if (rgbaChannels & WRITE_C)
        {
            THROW (
                IEX_NAMESPACE::ArgExc,
                "Cannot open file \""
                    << fileName
                    << "\" "
                       "for writing.  Tiled image files do not "
                       "support subsampled chroma channels.");
        }
    }
    else
    {
        if (rgbaChannels & WRITE_R) ch.insert ("R", Channel (HALF, 1, 1));

        if (rgbaChannels & WRITE_G) ch.insert ("G", Channel (HALF, 1, 1));

        if (rgbaChannels & WRITE_B) ch.insert ("B", Channel (HALF, 1, 1));
    }

    if (rgbaChannels & WRITE_A) ch.insert ("A", Channel (HALF, 1, 1));

    header.channels () = ch;
}

RgbaChannels
rgbaChannels (const ChannelList& ch, const string& channelNamePrefix = "")
{
    int i = 0;

    if (ch.findChannel (channelNamePrefix + "R")) i |= WRITE_R;

    if (ch.findChannel (channelNamePrefix + "G")) i |= WRITE_G;

    if (ch.findChannel (channelNamePrefix + "B")) i |= WRITE_B;

    if (ch.findChannel (channelNamePrefix + "A")) i |= WRITE_A;

    if (ch.findChannel (channelNamePrefix + "Y")) i |= WRITE_Y;

    return RgbaChannels (i);
}

string
prefixFromLayerName (const string& layerName, const Header& header)
{
    if (layerName.empty ()) return "";

    if (hasMultiView (header) && multiView (header)[0] == layerName) return "";

    return layerName + ".";
}

V3f
ywFromHeader (const Header& header)
{
    Chromaticities cr;

    if (hasChromaticities (header)) cr = chromaticities (header);

    return computeYw (cr);
}

} // namespace

class TiledRgbaOutputFile::ToYa
#if ILMTHREAD_THREADING_ENABLED
    : public std::mutex
#endif
{
public:
    ToYa (TiledOutputFile& outputFile, RgbaChannels rgbaChannels);

    void setFrameBuffer (const Rgba* base, size_t xStride, size_t yStride);

    void writeTile (int dx, int dy, int lx, int ly);

private:
    TiledOutputFile& _outputFile;
    bool             _writeA;
    unsigned int     _tileXSize;
    unsigned int     _tileYSize;
    V3f              _yw;
    Array2D<Rgba>    _buf;
    const Rgba*      _fbBase;
    size_t           _fbXStride;
    size_t           _fbYStride;
};

TiledRgbaOutputFile::ToYa::ToYa (
    TiledOutputFile& outputFile, RgbaChannels rgbaChannels)
    : _outputFile (outputFile)
{
    _writeA = (rgbaChannels & WRITE_A) ? true : false;

    const TileDescription& td = outputFile.header ().tileDescription ();

    _tileXSize = td.xSize;
    _tileYSize = td.ySize;
    _yw        = ywFromHeader (_outputFile.header ());
    _buf.resizeErase (_tileYSize, _tileXSize);
    _fbBase    = 0;
    _fbXStride = 0;
    _fbYStride = 0;
}

void
TiledRgbaOutputFile::ToYa::setFrameBuffer (
    const Rgba* base, size_t xStride, size_t yStride)
{
    _fbBase    = base;
    _fbXStride = xStride;
    _fbYStride = yStride;
}

void
TiledRgbaOutputFile::ToYa::writeTile (int dx, int dy, int lx, int ly)
{
    if (_fbBase == 0)
    {
        THROW (
            IEX_NAMESPACE::ArgExc,
            "No frame buffer was specified as the "
            "pixel data source for image file "
            "\"" << _outputFile.fileName ()
                 << "\".");
    }

    //
    // Copy the tile's RGBA pixels into _buf and convert
    // them to luminance/alpha format
    //

    Box2i dw    = _outputFile.dataWindowForTile (dx, dy, lx, ly);
    int   width = dw.max.x - dw.min.x + 1;

    intptr_t base = reinterpret_cast<intptr_t> (_fbBase);
    for (int y = dw.min.y, y1 = 0; y <= dw.max.y; ++y, ++y1)
    {
        for (int x = dw.min.x, x1 = 0; x <= dw.max.x; ++x, ++x1)
        {
            Rgba* ptr = reinterpret_cast<Rgba*> (
                base + sizeof (Rgba) * (x * _fbXStride + y * _fbYStride));
            _buf[y1][x1] = *ptr;
        }
        RGBAtoYCA (_yw, width, _writeA, _buf[y1], _buf[y1]);
    }

    //
    // Store the contents of _buf in the output file
    //

    FrameBuffer fb;

    fb.insert (
        "Y",
        Slice (
            HALF,                                  // type
            (char*) &_buf[-dw.min.y][-dw.min.x].g, // base
            sizeof (Rgba),                         // xStride
            sizeof (Rgba) * _tileXSize));          // yStride

    fb.insert (
        "A",
        Slice (
            HALF,                                  // type
            (char*) &_buf[-dw.min.y][-dw.min.x].a, // base
            sizeof (Rgba),                         // xStride
            sizeof (Rgba) * _tileXSize));          // yStride

    _outputFile.setFrameBuffer (fb);
    _outputFile.writeTile (dx, dy, lx, ly);
}

TiledRgbaOutputFile::TiledRgbaOutputFile (
    const char        name[],
    const Header&     header,
    RgbaChannels      rgbaChannels,
    int               tileXSize,
    int               tileYSize,
    LevelMode         mode,
    LevelRoundingMode rmode,
    int               numThreads)
    : _outputFile (0), _toYa (0)
{
    Header hd (header);
    insertChannels (hd, rgbaChannels, name);
    hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode));
    _outputFile = new TiledOutputFile (name, hd, numThreads);

    if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels);
}

TiledRgbaOutputFile::TiledRgbaOutputFile (
    OPENEXR_IMF_INTERNAL_NAMESPACE::OStream& os,
    const Header&                            header,
    RgbaChannels                             rgbaChannels,
    int                                      tileXSize,
    int                                      tileYSize,
    LevelMode                                mode,
    LevelRoundingMode                        rmode,
    int                                      numThreads)
    : _outputFile (0), _toYa (0)
{
    Header hd (header);
    insertChannels (hd, rgbaChannels, os.fileName ());
    hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode));
    _outputFile = new TiledOutputFile (os, hd, numThreads);

    if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels);
}

TiledRgbaOutputFile::TiledRgbaOutputFile (
    const char                    name[],
    int                           tileXSize,
    int                           tileYSize,
    LevelMode                     mode,
    LevelRoundingMode             rmode,
    const IMATH_NAMESPACE::Box2i& displayWindow,
    const IMATH_NAMESPACE::Box2i& dataWindow,
    RgbaChannels                  rgbaChannels,
    float                         pixelAspectRatio,
    const IMATH_NAMESPACE::V2f    screenWindowCenter,
    float                         screenWindowWidth,
    LineOrder                     lineOrder,
    Compression                   compression,
    int                           numThreads)
    : _outputFile (0), _toYa (0)
{
    Header hd (
        displayWindow,
        dataWindow.isEmpty () ? displayWindow : dataWindow,
        pixelAspectRatio,
        screenWindowCenter,
        screenWindowWidth,
        lineOrder,
        compression);

    insertChannels (hd, rgbaChannels, name);
    hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode));
    _outputFile = new TiledOutputFile (name, hd, numThreads);

    if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels);
}

TiledRgbaOutputFile::TiledRgbaOutputFile (
    const char                 name[],
    int                        width,
    int                        height,
    int                        tileXSize,
    int                        tileYSize,
    LevelMode                  mode,
    LevelRoundingMode          rmode,
    RgbaChannels               rgbaChannels,
    float                      pixelAspectRatio,
    const IMATH_NAMESPACE::V2f screenWindowCenter,
    float                      screenWindowWidth,
    LineOrder                  lineOrder,
    Compression                compression,
    int                        numThreads)
    : _outputFile (0), _toYa (0)
{
    Header hd (
        width,
        height,
        pixelAspectRatio,
        screenWindowCenter,
        screenWindowWidth,
        lineOrder,
        compression);

    insertChannels (hd, rgbaChannels, name);
    hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode));
    _outputFile = new TiledOutputFile (name, hd, numThreads);

    if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels);
}

TiledRgbaOutputFile::~TiledRgbaOutputFile ()
{
    delete _outputFile;
    delete _toYa;
}

void
TiledRgbaOutputFile::setFrameBuffer (
    const Rgba* base, size_t xStride, size_t yStride)
{
    if (_toYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_toYa);
#endif
        _toYa->setFrameBuffer (base, xStride, yStride);
    }
    else
    {
        size_t xs = xStride * sizeof (Rgba);
        size_t ys = yStride * sizeof (Rgba);

        FrameBuffer fb;

        fb.insert ("R", Slice (HALF, (char*) &base[0].r, xs, ys));
        fb.insert ("G", Slice (HALF, (char*) &base[0].g, xs, ys));
        fb.insert ("B", Slice (HALF, (char*) &base[0].b, xs, ys));
        fb.insert ("A", Slice (HALF, (char*) &base[0].a, xs, ys));

        _outputFile->setFrameBuffer (fb);
    }
}

const Header&
TiledRgbaOutputFile::header () const
{
    return _outputFile->header ();
}

const FrameBuffer&
TiledRgbaOutputFile::frameBuffer () const
{
    return _outputFile->frameBuffer ();
}

const IMATH_NAMESPACE::Box2i&
TiledRgbaOutputFile::displayWindow () const
{
    return _outputFile->header ().displayWindow ();
}

const IMATH_NAMESPACE::Box2i&
TiledRgbaOutputFile::dataWindow () const
{
    return _outputFile->header ().dataWindow ();
}

float
TiledRgbaOutputFile::pixelAspectRatio () const
{
    return _outputFile->header ().pixelAspectRatio ();
}

const IMATH_NAMESPACE::V2f
TiledRgbaOutputFile::screenWindowCenter () const
{
    return _outputFile->header ().screenWindowCenter ();
}

float
TiledRgbaOutputFile::screenWindowWidth () const
{
    return _outputFile->header ().screenWindowWidth ();
}

LineOrder
TiledRgbaOutputFile::lineOrder () const
{
    return _outputFile->header ().lineOrder ();
}

Compression
TiledRgbaOutputFile::compression () const
{
    return _outputFile->header ().compression ();
}

RgbaChannels
TiledRgbaOutputFile::channels () const
{
    return rgbaChannels (_outputFile->header ().channels ());
}

unsigned int
TiledRgbaOutputFile::tileXSize () const
{
    return _outputFile->tileXSize ();
}

unsigned int
TiledRgbaOutputFile::tileYSize () const
{
    return _outputFile->tileYSize ();
}

LevelMode
TiledRgbaOutputFile::levelMode () const
{
    return _outputFile->levelMode ();
}

LevelRoundingMode
TiledRgbaOutputFile::levelRoundingMode () const
{
    return _outputFile->levelRoundingMode ();
}

int
TiledRgbaOutputFile::numLevels () const
{
    return _outputFile->numLevels ();
}

int
TiledRgbaOutputFile::numXLevels () const
{
    return _outputFile->numXLevels ();
}

int
TiledRgbaOutputFile::numYLevels () const
{
    return _outputFile->numYLevels ();
}

bool
TiledRgbaOutputFile::isValidLevel (int lx, int ly) const
{
    return _outputFile->isValidLevel (lx, ly);
}

int
TiledRgbaOutputFile::levelWidth (int lx) const
{
    return _outputFile->levelWidth (lx);
}

int
TiledRgbaOutputFile::levelHeight (int ly) const
{
    return _outputFile->levelHeight (ly);
}

int
TiledRgbaOutputFile::numXTiles (int lx) const
{
    return _outputFile->numXTiles (lx);
}

int
TiledRgbaOutputFile::numYTiles (int ly) const
{
    return _outputFile->numYTiles (ly);
}

IMATH_NAMESPACE::Box2i
TiledRgbaOutputFile::dataWindowForLevel (int l) const
{
    return _outputFile->dataWindowForLevel (l);
}

IMATH_NAMESPACE::Box2i
TiledRgbaOutputFile::dataWindowForLevel (int lx, int ly) const
{
    return _outputFile->dataWindowForLevel (lx, ly);
}

IMATH_NAMESPACE::Box2i
TiledRgbaOutputFile::dataWindowForTile (int dx, int dy, int l) const
{
    return _outputFile->dataWindowForTile (dx, dy, l);
}

IMATH_NAMESPACE::Box2i
TiledRgbaOutputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const
{
    return _outputFile->dataWindowForTile (dx, dy, lx, ly);
}

void
TiledRgbaOutputFile::writeTile (int dx, int dy, int l)
{
    if (_toYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_toYa);
#endif
        _toYa->writeTile (dx, dy, l, l);
    }
    else
    {
        _outputFile->writeTile (dx, dy, l);
    }
}

void
TiledRgbaOutputFile::writeTile (int dx, int dy, int lx, int ly)
{
    if (_toYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_toYa);
#endif
        _toYa->writeTile (dx, dy, lx, ly);
    }
    else
    {
        _outputFile->writeTile (dx, dy, lx, ly);
    }
}

void
TiledRgbaOutputFile::writeTiles (
    int dxMin, int dxMax, int dyMin, int dyMax, int lx, int ly)
{
    if (_toYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_toYa);
#endif
        for (int dy = dyMin; dy <= dyMax; dy++)
            for (int dx = dxMin; dx <= dxMax; dx++)
                _toYa->writeTile (dx, dy, lx, ly);
    }
    else
    {
        _outputFile->writeTiles (dxMin, dxMax, dyMin, dyMax, lx, ly);
    }
}

void
TiledRgbaOutputFile::writeTiles (
    int dxMin, int dxMax, int dyMin, int dyMax, int l)
{
    writeTiles (dxMin, dxMax, dyMin, dyMax, l, l);
}

class TiledRgbaInputFile::FromYa
#if ILMTHREAD_THREADING_ENABLED
    : public std::mutex
#endif
{
public:
    FromYa (TiledInputFile& inputFile);

    void setFrameBuffer (
        Rgba*         base,
        size_t        xStride,
        size_t        yStride,
        const string& channelNamePrefix);

    void readTile (int dx, int dy, int lx, int ly);

private:
    TiledInputFile& _inputFile;
    unsigned int    _tileXSize;
    unsigned int    _tileYSize;
    V3f             _yw;
    Array2D<Rgba>   _buf;
    Rgba*           _fbBase;
    size_t          _fbXStride;
    size_t          _fbYStride;
};

TiledRgbaInputFile::FromYa::FromYa (TiledInputFile& inputFile)
    : _inputFile (inputFile)
{
    const TileDescription& td = inputFile.header ().tileDescription ();

    _tileXSize = td.xSize;
    _tileYSize = td.ySize;
    _yw        = ywFromHeader (_inputFile.header ());
    _buf.resizeErase (_tileYSize, _tileXSize);
    _fbBase    = 0;
    _fbXStride = 0;
    _fbYStride = 0;
}

void
TiledRgbaInputFile::FromYa::setFrameBuffer (
    Rgba* base, size_t xStride, size_t yStride, const string& channelNamePrefix)
{
    if (_fbBase == 0)
    {
        FrameBuffer fb;

        fb.insert (
            channelNamePrefix + "Y",
            Slice (
                HALF,                       // type
                (char*) &_buf[0][0].g,      // base
                sizeof (Rgba),              // xStride
                sizeof (Rgba) * _tileXSize, // yStride
                1,
                1,   // sampling
                0.0, // fillValue
                true,
                true)); // tileCoordinates

        fb.insert (
            channelNamePrefix + "A",
            Slice (
                HALF,                       // type
                (char*) &_buf[0][0].a,      // base
                sizeof (Rgba),              // xStride
                sizeof (Rgba) * _tileXSize, // yStride
                1,
                1,   // sampling
                1.0, // fillValue
                true,
                true)); // tileCoordinates

        _inputFile.setFrameBuffer (fb);
    }

    _fbBase    = base;
    _fbXStride = xStride;
    _fbYStride = yStride;
}

void
TiledRgbaInputFile::FromYa::readTile (int dx, int dy, int lx, int ly)
{
    if (_fbBase == 0)
    {
        THROW (
            IEX_NAMESPACE::ArgExc,
            "No frame buffer was specified as the "
            "pixel data destination for image file "
            "\"" << _inputFile.fileName ()
                 << "\".");
    }

    //
    // Read the tile requested by the caller into _buf.
    //

    _inputFile.readTile (dx, dy, lx, ly);

    //
    // Convert the luminance/alpha pixels to RGBA
    // and copy them into the caller's frame buffer.
    //

    Box2i    dw    = _inputFile.dataWindowForTile (dx, dy, lx, ly);
    int      width = dw.max.x - dw.min.x + 1;
    intptr_t base  = reinterpret_cast<intptr_t> (_fbBase);

    for (int y = dw.min.y, y1 = 0; y <= dw.max.y; ++y, ++y1)
    {
        for (int x1 = 0; x1 < width; ++x1)
        {
            _buf[y1][x1].r = 0;
            _buf[y1][x1].b = 0;
        }

        YCAtoRGBA (_yw, width, _buf[y1], _buf[y1]);

        for (int x = dw.min.x, x1 = 0; x <= dw.max.x; ++x, ++x1)
        {
            Rgba* ptr = reinterpret_cast<Rgba*> (
                base + sizeof (Rgba) * (x * _fbXStride + y * _fbYStride));
            *ptr = _buf[y1][x1];
        }
    }
}

TiledRgbaInputFile::TiledRgbaInputFile (const char name[], int numThreads)
    : _inputFile (new TiledInputFile (name, numThreads))
    , _fromYa (0)
    , _channelNamePrefix ("")
{
    if (channels () & WRITE_Y) _fromYa = new FromYa (*_inputFile);
}

TiledRgbaInputFile::TiledRgbaInputFile (
    OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int numThreads)
    : _inputFile (new TiledInputFile (is, numThreads))
    , _fromYa (0)
    , _channelNamePrefix ("")
{
    if (channels () & WRITE_Y) _fromYa = new FromYa (*_inputFile);
}

TiledRgbaInputFile::TiledRgbaInputFile (
    const char name[], const string& layerName, int numThreads)
    : _inputFile (new TiledInputFile (name, numThreads))
    , _fromYa (0)
    , _channelNamePrefix (
          prefixFromLayerName (layerName, _inputFile->header ()))
{
    if (channels () & WRITE_Y) _fromYa = new FromYa (*_inputFile);
}

TiledRgbaInputFile::TiledRgbaInputFile (
    OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is,
    const string&                            layerName,
    int                                      numThreads)
    : _inputFile (new TiledInputFile (is, numThreads))
    , _fromYa (0)
    , _channelNamePrefix (
          prefixFromLayerName (layerName, _inputFile->header ()))
{
    if (channels () & WRITE_Y) _fromYa = new FromYa (*_inputFile);
}

TiledRgbaInputFile::~TiledRgbaInputFile ()
{
    delete _inputFile;
    delete _fromYa;
}

void
TiledRgbaInputFile::setFrameBuffer (Rgba* base, size_t xStride, size_t yStride)
{
    if (_fromYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_fromYa);
#endif
        _fromYa->setFrameBuffer (base, xStride, yStride, _channelNamePrefix);
    }
    else
    {
        size_t xs = xStride * sizeof (Rgba);
        size_t ys = yStride * sizeof (Rgba);

        FrameBuffer fb;

        fb.insert (
            _channelNamePrefix + "R",
            Slice (
                HALF,
                (char*) &base[0].r,
                xs,
                ys,
                1,
                1,     // xSampling, ySampling
                0.0)); // fillValue

        fb.insert (
            _channelNamePrefix + "G",
            Slice (
                HALF,
                (char*) &base[0].g,
                xs,
                ys,
                1,
                1,     // xSampling, ySampling
                0.0)); // fillValue

        fb.insert (
            _channelNamePrefix + "B",
            Slice (
                HALF,
                (char*) &base[0].b,
                xs,
                ys,
                1,
                1,     // xSampling, ySampling
                0.0)); // fillValue

        fb.insert (
            _channelNamePrefix + "A",
            Slice (
                HALF,
                (char*) &base[0].a,
                xs,
                ys,
                1,
                1,     // xSampling, ySampling
                1.0)); // fillValue

        _inputFile->setFrameBuffer (fb);
    }
}

void
TiledRgbaInputFile::setLayerName (const std::string& layerName)
{
    delete _fromYa;
    _fromYa = 0;

    _channelNamePrefix = prefixFromLayerName (layerName, _inputFile->header ());

    if (channels () & WRITE_Y) _fromYa = new FromYa (*_inputFile);

    FrameBuffer fb;
    _inputFile->setFrameBuffer (fb);
}

const Header&
TiledRgbaInputFile::header () const
{
    return _inputFile->header ();
}

const char*
TiledRgbaInputFile::fileName () const
{
    return _inputFile->fileName ();
}

const FrameBuffer&
TiledRgbaInputFile::frameBuffer () const
{
    return _inputFile->frameBuffer ();
}

const IMATH_NAMESPACE::Box2i&
TiledRgbaInputFile::displayWindow () const
{
    return _inputFile->header ().displayWindow ();
}

const IMATH_NAMESPACE::Box2i&
TiledRgbaInputFile::dataWindow () const
{
    return _inputFile->header ().dataWindow ();
}

float
TiledRgbaInputFile::pixelAspectRatio () const
{
    return _inputFile->header ().pixelAspectRatio ();
}

const IMATH_NAMESPACE::V2f
TiledRgbaInputFile::screenWindowCenter () const
{
    return _inputFile->header ().screenWindowCenter ();
}

float
TiledRgbaInputFile::screenWindowWidth () const
{
    return _inputFile->header ().screenWindowWidth ();
}

LineOrder
TiledRgbaInputFile::lineOrder () const
{
    return _inputFile->header ().lineOrder ();
}

Compression
TiledRgbaInputFile::compression () const
{
    return _inputFile->header ().compression ();
}

RgbaChannels
TiledRgbaInputFile::channels () const
{
    return rgbaChannels (_inputFile->header ().channels (), _channelNamePrefix);
}

int
TiledRgbaInputFile::version () const
{
    return _inputFile->version ();
}

bool
TiledRgbaInputFile::isComplete () const
{
    return _inputFile->isComplete ();
}

unsigned int
TiledRgbaInputFile::tileXSize () const
{
    return _inputFile->tileXSize ();
}

unsigned int
TiledRgbaInputFile::tileYSize () const
{
    return _inputFile->tileYSize ();
}

LevelMode
TiledRgbaInputFile::levelMode () const
{
    return _inputFile->levelMode ();
}

LevelRoundingMode
TiledRgbaInputFile::levelRoundingMode () const
{
    return _inputFile->levelRoundingMode ();
}

int
TiledRgbaInputFile::numLevels () const
{
    return _inputFile->numLevels ();
}

int
TiledRgbaInputFile::numXLevels () const
{
    return _inputFile->numXLevels ();
}

int
TiledRgbaInputFile::numYLevels () const
{
    return _inputFile->numYLevels ();
}

bool
TiledRgbaInputFile::isValidLevel (int lx, int ly) const
{
    return _inputFile->isValidLevel (lx, ly);
}

int
TiledRgbaInputFile::levelWidth (int lx) const
{
    return _inputFile->levelWidth (lx);
}

int
TiledRgbaInputFile::levelHeight (int ly) const
{
    return _inputFile->levelHeight (ly);
}

int
TiledRgbaInputFile::numXTiles (int lx) const
{
    return _inputFile->numXTiles (lx);
}

int
TiledRgbaInputFile::numYTiles (int ly) const
{
    return _inputFile->numYTiles (ly);
}

IMATH_NAMESPACE::Box2i
TiledRgbaInputFile::dataWindowForLevel (int l) const
{
    return _inputFile->dataWindowForLevel (l);
}

IMATH_NAMESPACE::Box2i
TiledRgbaInputFile::dataWindowForLevel (int lx, int ly) const
{
    return _inputFile->dataWindowForLevel (lx, ly);
}

IMATH_NAMESPACE::Box2i
TiledRgbaInputFile::dataWindowForTile (int dx, int dy, int l) const
{
    return _inputFile->dataWindowForTile (dx, dy, l);
}

IMATH_NAMESPACE::Box2i
TiledRgbaInputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const
{
    return _inputFile->dataWindowForTile (dx, dy, lx, ly);
}

void
TiledRgbaInputFile::readTile (int dx, int dy, int l)
{
    if (_fromYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_fromYa);
#endif
        _fromYa->readTile (dx, dy, l, l);
    }
    else
    {
        _inputFile->readTile (dx, dy, l);
    }
}

void
TiledRgbaInputFile::readTile (int dx, int dy, int lx, int ly)
{
    if (_fromYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_fromYa);
#endif
        _fromYa->readTile (dx, dy, lx, ly);
    }
    else
    {
        _inputFile->readTile (dx, dy, lx, ly);
    }
}

void
TiledRgbaInputFile::readTiles (
    int dxMin, int dxMax, int dyMin, int dyMax, int lx, int ly)
{
    if (_fromYa)
    {
#if ILMTHREAD_THREADING_ENABLED
        std::lock_guard<std::mutex> lock (*_fromYa);
#endif
        for (int dy = dyMin; dy <= dyMax; dy++)
            for (int dx = dxMin; dx <= dxMax; dx++)
                _fromYa->readTile (dx, dy, lx, ly);
    }
    else
    {
        _inputFile->readTiles (dxMin, dxMax, dyMin, dyMax, lx, ly);
    }
}

void
TiledRgbaInputFile::readTiles (
    int dxMin, int dxMax, int dyMin, int dyMax, int l)
{
    readTiles (dxMin, dxMax, dyMin, dyMax, l, l);
}

void
TiledRgbaOutputFile::updatePreviewImage (const PreviewRgba newPixels[])
{
    _outputFile->updatePreviewImage (newPixels);
}

void
TiledRgbaOutputFile::breakTile (
    int dx, int dy, int lx, int ly, int offset, int length, char c)
{
    _outputFile->breakTile (dx, dy, lx, ly, offset, length, c);
}

OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
