/*******************************************************************************
* Copyright 2016-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#if (defined _DEBUG && defined _WIN32)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif

#include <math.h>
#include <memory>

#include "iw++/iw.hpp"

#include "base.h"
#include "base_image.h"
#include "base_iw.h"
#include "base_renderer.h"

static void printVersion()
{
    printf("\nIntel(R) IPP Integration Wrappers Example: Advanced Tiling Demo");
    printf("\nThis example demonstrates pipeline tiling in step-by-step manner.\nGUI is required for demonstration process.\n");
    printf("\nBased on:");
    printf("\nIntel(R) IPP Integration Wrappers: %s", ipp::IwVersion().getInfoString().c_str());
    printf("\nIntel(R) IPP: %s\n", ipp::IppVersion().getInfoString().c_str());
}

static void printHelp(const cmd::OptDef pOptions[], char* argv[])
{
    printf("\nUsage: %s [-i] InputFile [Options]\n", GetProgName(argv));
    printf("Options:\n");
    cmd::OptUsage(pOptions);
}

static void Collage(const std::vector<Image> &images, const std::vector<Rect> &rects, Image &dst, size_t interval, size_t padding)
{
    Size maxSize;
    unsigned int thickness = 2;
    for(size_t i = 0; i < images.size(); i++)
    {
        maxSize.width  = maxSize.width + images[i].m_size.width + ((i < images.size()-1)?interval:0);
        maxSize.height = MAX(maxSize.height, images[i].m_size.height);
    }
    maxSize.width += padding*2;
    maxSize.height += padding*2;

    dst.m_size = maxSize;
    dst.Alloc();
    dst.Set(-MAX_DOUBLE_ABS);
    long long shift = 0;
    for(size_t i = 0; i < images.size(); i++)
    {
        Image cvt = images[i];
        Point pnt(shift + padding, padding);

        cvt.ConvertSamples(dst.m_sampleFormat, NULL);
        cvt.ConvertColor(dst.m_color);
        cvt.MapTo(dst, pnt);
        if(rects.size())
        {
            if(i > 0) // First doesn't have dst ROI
            {
                {
                    Rect rect(
                        rects[i*2-1].x + pnt.x - thickness,
                        rects[i*2-1].y + pnt.y - thickness,
                        ((rects[i*2-1].width)?rects[i*2-1].width:cvt.m_size.width-rects[i*2-1].x)+thickness*2,
                        ((rects[i*2-1].height)?rects[i*2-1].height:cvt.m_size.height-rects[i*2-1].y)+thickness*2
                        );
                    dst.DrawRect(rect, (unsigned char*)defPalette[i%8-1], thickness);
                }
            }
            if(i < images.size() - 1) // Last doesn't have src ROI
            {
                {
                    Rect rect(
                        rects[i*2].x + pnt.x - thickness,
                        rects[i*2].y + pnt.y - thickness,
                        ((rects[i*2].width)?rects[i*2].width:cvt.m_size.width-rects[i*2].x)+thickness*2,
                        ((rects[i*2].height)?rects[i*2].height:cvt.m_size.height-rects[i*2].y)+thickness*2
                        );
                    dst.DrawRect(rect, (unsigned char*)defPalette[i%8], thickness);
                }
            }
        }

        shift += (images[i].m_size.width + interval);
    }
}

static void Draw(WindowDraw &draw, const std::vector<Image> &images, const std::vector<Rect> &rects, bool wait = true)
{
    if(draw.IsInitialized())
    {
        int drawRoi = 1;
        int scale   = 1;
        Image display(Size(), CF_RGBA, ST_8U);
        {
            Collage(images, (drawRoi)?rects:std::vector<Rect>(), display, 10, 10);
        }

        if(scale != 1)
        {
            Image displaySc(display);
            displaySc.m_size = Size(display.m_size.width*scale, display.m_size.height*scale);
            displaySc.Alloc();

            ipp::IwiImage iwDisplay     = ImageToIwImage(display);
            ipp::IwiImage iwDisplaySc   = ImageToIwImage(displaySc);

            ipp::iwiResize(iwDisplay, iwDisplaySc, ippNearest);
            display = displaySc;
        }

        if(wait)
        {
            bool redraw = true;
            while(!draw.IsClosed())
            {
                vm_time_sleep(10);
                int key = draw.CheckKey();

                if(draw.IsInvalidated())
                    redraw = true;
                if(redraw)
                {
                    draw.DrawImage(&display);
                    redraw = false;
                }

                if(key == KK_SPACE)
                    break;
            }
        }
        else
        {
            vm_time_sleep(10);
            draw.DrawImage(&display);
        }
    }
}

class TilingBase
{
public:
    TilingBase()
    {
    }

    static Status InitExternal(Image &src, Image &dst)
    {
        src.ConvertColor(m_srcColor);
        src.ConvertSamples(m_srcType);

        dst.Alloc(src.m_size, m_dstColor, m_dstType);

        return STS_OK;
    }

public:
    static IwiDerivativeType   m_sobelType;
    static IppiMaskSize        m_sobelMask;
    static IppiMaskSize        m_sharpMask;
    static int                 m_gaussMask;
    static ipp::IwiBorderType  m_border;

    static SampleFormat        m_srcType;
    static SampleFormat        m_dstType;
    static SampleFormat        m_interType;
    static ColorFormat         m_srcColor;
    static ColorFormat         m_dstColor;
};

IwiDerivativeType   TilingBase::m_sobelType = iwiDerivHorFirst;
IppiMaskSize        TilingBase::m_sobelMask = ippMskSize5x5;
IppiMaskSize        TilingBase::m_sharpMask = ippMskSize3x3;
int                 TilingBase::m_gaussMask = 3;
ipp::IwiBorderType  TilingBase::m_border    = ippBorderRepl;

SampleFormat        TilingBase::m_srcType   = ST_8U;
SampleFormat        TilingBase::m_dstType   = ST_8U;
SampleFormat        TilingBase::m_interType = ST_32F;
ColorFormat         TilingBase::m_srcColor  = CF_RGB;
ColorFormat         TilingBase::m_dstColor  = CF_GRAY;

class TilingDemo: public TilingBase
{
public:
    TilingDemo()
    {
    }
    ~TilingDemo()
    {
    }

    Status Init(Image &src, Image &dst, Size maxTile)
    {
        InitExternal(src, dst);

        // Convert filter masks to borders size
        m_sobelBorderSize = ipp::iwiSizeToBorderSize(iwiMaskToSize(m_sobelMask));
        m_sharpBorderSize = ipp::iwiSizeToBorderSize(iwiMaskToSize(m_sharpMask));
        m_gaussBorderSize = ipp::iwiSizeToBorderSize(m_gaussMask);

        try
        {
            // If we have complex pipeline with several functions which have borders and we want to process whole pipeline by
            // tiles, we need to make sure that tiles for each function is positioned with precise offset and size according to requirements of previous function parameters.
            // iwiTilePipeline functions create chain of ROIs to automatically track dependencies between tiles for different functions in pipeline.
            //
            // Advanced tiles initialization must be performed in reverse order: from dst to src. Tiles are navigated
            // relative to final dst image.
            //
            // This function will initialize Tile for the final operation: iwiScale 32f->8u
            m_tile[5].Init(ipp::IwiSize((IwSize)maxTile.width, (IwSize)maxTile.height), ipp::IwiSize((IwSize)dst.m_size.width, (IwSize)dst.m_size.height));

            // Initialize Tile for the ippiFilterSharpenBorder
            m_tile[4].InitChild(m_tile[5], m_border, m_sharpBorderSize);

            // Initialize Tile for the iwiFilterSobel
            m_tile[3].InitChild(m_tile[4], m_border, m_sobelBorderSize);

            // Initialize Tile for the iwiFilterGaussian
            m_tile[2].InitChild(m_tile[3], m_border, m_gaussBorderSize);

            // Initialize Tile for the iwiScale 8u->32f
            m_tile[1].InitChild(m_tile[2]);

            // Initialize Tile for the iwiColorConvert
            m_tile[0].InitChild(m_tile[1]);

            ipp::IwiSize minTile = m_tile[0].GetMinTileSize();
            if(maxTile.width < minTile.width || maxTile.height < minTile.height)
            {
                PRINT_MESSAGE("Tile size is too small for the pipeline");
                return STS_ERR_INVALID_PARAMS;
            }

            // Allocate intermediate buffers
            m_inter[0].Alloc(IppSizeToImage(m_tile[0].GetDstBufferSize()), m_dstColor, m_srcType);
            m_inter[1].Alloc(IppSizeToImage(m_tile[1].GetDstBufferSize()), m_dstColor, m_interType);
            m_inter[2].Alloc(IppSizeToImage(m_tile[2].GetDstBufferSize()), m_dstColor, m_interType);
            m_inter[3].Alloc(IppSizeToImage(m_tile[3].GetDstBufferSize()), m_dstColor, m_interType);
            m_inter[4].Alloc(IppSizeToImage(m_tile[4].GetDstBufferSize()), m_dstColor, m_interType);
        }
        catch(ipp::IwException ex)
        {
            CHECK_STATUS_PRINT_AC(ex.m_status, "catch(IwException)", iwGetStatusString(ex), return STS_ERR_FAILED);
        }

        return STS_OK;
    }

    Status Run(Image &src, Image &dst, Rect tile)
    {
        try
        {
            ipp::IwiRoi      ippTile((IwSize)tile.x, (IwSize)tile.y, (IwSize)tile.width, (IwSize)tile.height);
            ipp::IwiColorFmt ippSrcColor  = ImageColorToIpp(m_srcColor);
            ipp::IwiColorFmt ippDstColor  = ImageColorToIpp(m_dstColor);

            ipp::IwiImage iwSrc = ImageToIwImage(src);
            ipp::IwiImage iwDst = ImageToIwImage(dst);
            ipp::IwiImage iwInter[5] = {
                ImageToIwImage(m_inter[0]),
                ImageToIwImage(m_inter[1]),
                ImageToIwImage(m_inter[2]),
                ImageToIwImage(m_inter[3]),
                ImageToIwImage(m_inter[4])
            };

            // Clear buffers for better preview
            m_inter[0].Set(-MAX_DOUBLE_ABS);
            m_inter[1].Set(-MAX_DOUBLE_ABS);
            m_inter[2].Set(-MAX_DOUBLE_ABS);
            m_inter[3].Set(-MAX_DOUBLE_ABS);
            m_inter[4].Set(-MAX_DOUBLE_ABS);

            // Set Tile for ROI chain
            // This function updates ROIs in chain to align according to current tile coordinates and size.
            // SetTile method can be called only once for particular tile and for any ROI in the chain. It will
            // automatically find top ROI in chain and propagate tile parameters correctly.
            m_tile[5].SetTile(ippTile);

            // Stage 1. Color conversion
            ipp::iwiColorConvert(iwSrc, ippSrcColor, iwInter[0], ippDstColor, IwValueMax, ipp::IwDefault(), m_tile[0]);

            // Stage 2. Scaling 8u->32f
            double mul, add;
            ipp::iwiScale_GetScaleVals(iwInter[0].m_dataType, iwInter[1].m_dataType, mul, add);
            ipp::iwiScale(iwInter[0], iwInter[1], mul, add, ipp::IwDefault(), m_tile[1]);

            // Stage 3. Gaussian filter
            ipp::iwiFilterGaussian(iwInter[1], iwInter[2], m_gaussMask, 1, ipp::IwDefault(), m_border, m_tile[2]);

            // Stage 4. Sobel filter
            ipp::iwiFilterSobel(iwInter[2], iwInter[3], m_sobelType, m_sobelMask, ipp::IwDefault(), m_border, m_tile[3]);

            // Stage 5. Sharping.
            // This Intel(R) IPP function is given here as an example of usage of any non-IW function with advanced tiling.
            // To use advanced tiling with non-IW function some manual steps must be taken.
            // 1. IwiTile functions assume that IW function will make buffer boundary check, so we need to perform such
            //    check to prevent Out Of Buffer access.
            // 2. IW function shifts buffer according to local buffer offset in IwiTile
            // 3. Border flags must be correctly set for functions with borders according to current local and absolute
            //    positions
            // 4. Border should be reconstructed according to the current tile position.
            // There are special functions which allow to perform all these steps just like in IW functions.
            {
                IppStatus          ippStatus;
                ipp::IwiBorderType sharpBorder;
                IppiSize           sharpSize;

                // Step 1: Create local sub-images according to current tile parameters.
                ipp::IwiImage sharpSrc = iwInter[3].GetRoiImage(m_tile[4].GetBoundedSrcRoi());
                ipp::IwiImage sharpDst = iwInter[4].GetRoiImage(m_tile[4].GetBoundedDstRoi());

                // Step 2: Update inMem flags in border variable according to current tile position
                // These border values are for Intel(R) IPP functions, but for non- Intel(R) IPP functions it should be similar, if they
                // support manual borders memory type parameters. You can check border for specific flags
                // (e.g.: border&ippBorderInMemLeft) and convert them into flags suitable for your functionality.
                sharpBorder = m_tile[4].GetTileBorder(m_border);

                // Step 3: Border reconstruction. This function will build border for intermediate steps to make
                // image borders "transparent" for the function.
                m_tile[4].BuildBorder(sharpSrc, sharpBorder);

                // Since src and dst buffers may differ, get minimal size.
                sharpSize.width  = (int)IPP_MIN(sharpSrc.m_size.width,  sharpDst.m_size.width);
                sharpSize.height = (int)IPP_MIN(sharpSrc.m_size.height, sharpDst.m_size.height);

                // Allocate buffer for the function.
                {
                    int bufferSize;

                    ippStatus = ippiFilterSharpenBorderGetBufferSize(sharpSize, m_sharpMask, sharpSrc.m_dataType, sharpDst.m_dataType, sharpSrc.m_channels, &bufferSize);
                    CHECK_STATUS_PRINT_AC(ippStatus, "ippiFilterSharpenBorderGetBufferSize()", ippGetStatusString(ippStatus), return STS_ERR_FAILED);

                    if(bufferSize && bufferSize > (int)m_sharpBuffer.GetSize())
                    {
                        m_sharpBuffer.Alloc(bufferSize);
                        if(!m_sharpBuffer)
                        {
                            PRINT_MESSAGE("Cannot allocate memory for ippiFilterSharpenBorder_32f_C1R");
                            return STS_ERR_ALLOC;
                        }
                    }
                }

                ippStatus = ippiFilterSharpenBorder_32f_C1R((Ipp32f*)sharpSrc.ptr(), (int)sharpSrc.m_step, (Ipp32f*)sharpDst.ptr(), (int)sharpDst.m_step, sharpSize, m_sharpMask, sharpBorder, 0, m_sharpBuffer);
                CHECK_STATUS_PRINT_AC(ippStatus, "ippiFilterSharpenBorder_32f_C1R()", ippGetStatusString(ippStatus), return STS_ERR_FAILED);
            }

            // Stage 6. Scaling 32f->8u
            ipp::iwiScale_GetScaleVals(iwInter[4].m_dataType, iwDst.m_dataType, mul, add);
            ipp::iwiScale(iwInter[4], iwDst, mul, add, ipp::IwDefault(), m_tile[5]);
        }
        catch(ipp::IwException ex)
        {
            CHECK_STATUS_PRINT_AC(ex.m_status, "catch(IwException)", iwGetStatusString(ex), return STS_ERR_FAILED);
        }

        return STS_OK;
    }

    void GetRects(std::vector<Rect> &vector)
    {
        size_t size = sizeof(m_tile)/sizeof(m_tile[0]);
        vector.reserve(size);

        for(size_t i = 0; i < size; i++)
        {
            vector.push_back(Rect(IwiRoiToImage(m_tile[i].GetBoundedSrcRoi())));
            vector.push_back(Rect(IwiRoiToImage(m_tile[i].GetBoundedDstRoi())));
        }
    }
    void GetBuffers(std::vector<Image> &vector)
    {
        size_t size = sizeof(m_inter)/sizeof(m_inter[0]);
        vector.reserve(size);

        for(size_t i = 0; i < size; i++)
            vector.push_back(m_inter[i]);
    }

public:
    Image                   m_inter[5]; // Array of intermediate buffers for tiles chain
    ipp::IwiTilePipeline    m_tile[6];  // Array of tiling nodes

    ipp::IwiBorderSize m_sobelBorderSize;
    ipp::IwiBorderSize m_gaussBorderSize;
    ipp::IwiBorderSize m_sharpBorderSize;
    AutoBuffer<Ipp8u>  m_sharpBuffer;
};

class TilingDemoRef: public TilingBase
{
public:
    TilingDemoRef()
    {
    }
    ~TilingDemoRef()
    {
    }

    Status Init(Image &src, Image &dst)
    {
        InitExternal(src, dst);

        // Allocate intermediate buffers
        m_inter[0].Alloc(src.m_size, m_dstColor, m_srcType);
        m_inter[1].Alloc(src.m_size, m_dstColor, m_interType);
        m_inter[2].Alloc(src.m_size, m_dstColor, m_interType);
        m_inter[3].Alloc(src.m_size, m_dstColor, m_interType);
        m_inter[4].Alloc(src.m_size, m_dstColor, m_interType);

        return STS_OK;
    }

    Status Run(Image &src, Image &dst)
    {
        try
        {
            ipp::IwiColorFmt ippSrcColor  = ImageColorToIpp(m_srcColor);
            ipp::IwiColorFmt ippDstColor  = ImageColorToIpp(m_dstColor);

            ipp::IwiImage iwSrc = ImageToIwImage(src);
            ipp::IwiImage iwDst = ImageToIwImage(dst);
            ipp::IwiImage iwInter[5] = {
                ImageToIwImage(m_inter[0]),
                ImageToIwImage(m_inter[1]),
                ImageToIwImage(m_inter[2]),
                ImageToIwImage(m_inter[3]),
                ImageToIwImage(m_inter[4])
            };

            ipp::iwiColorConvert(iwSrc, ippSrcColor, iwInter[0], ippDstColor);

            double mul, add;
            ipp::iwiScale_GetScaleVals(iwInter[0].m_dataType, iwInter[1].m_dataType, mul, add);
            ipp::iwiScale(iwInter[0], iwInter[1], mul, add);

            ipp::iwiFilterGaussian(iwInter[1], iwInter[2], m_gaussMask, 1, ipp::IwDefault(), m_border);
            ipp::iwiFilterSobel(iwInter[2], iwInter[3], m_sobelType, m_sobelMask, ipp::IwDefault(), m_border);

            {
                IppStatus   ippStatus;
                IppiSize    sharpSize = {(int)iwInter[4].m_size.width, (int)iwInter[4].m_size.height};

                // Allocate buffer for the function.
                {
                    int bufferSize;

                    ippStatus = ippiFilterSharpenBorderGetBufferSize(sharpSize, m_sharpMask, iwInter[3].m_dataType, iwInter[4].m_dataType, iwInter[3].m_channels, &bufferSize);
                    CHECK_STATUS_PRINT_AC(ippStatus, "ippiFilterSharpenBorderGetBufferSize()", ippGetStatusString(ippStatus), return STS_ERR_FAILED);

                    if(bufferSize && bufferSize > (int)m_sharpBuffer.GetSize())
                    {
                        m_sharpBuffer.Alloc(bufferSize);
                        if(!m_sharpBuffer)
                        {
                            PRINT_MESSAGE("Cannot allocate memory for ippiFilterSharpenBorder_32f_C1R");
                            return STS_ERR_ALLOC;
                        }
                    }
                }

                ippStatus = ippiFilterSharpenBorder_32f_C1R((Ipp32f*)iwInter[3].ptr(), (int)iwInter[3].m_step, (Ipp32f*)iwInter[4].ptr(), (int)iwInter[4].m_step, sharpSize, m_sharpMask, m_border, 0, m_sharpBuffer);
                CHECK_STATUS_PRINT_AC(ippStatus, "ippiFilterSharpenBorder_32f_C1R()", ippGetStatusString(ippStatus), return STS_ERR_FAILED);
            }

            // Stage 6. Scaling 32f->8u
            ipp::iwiScale_GetScaleVals(iwInter[4].m_dataType, iwDst.m_dataType, mul, add);
            ipp::iwiScale(iwInter[4], iwDst, mul, add);
        }
        catch(ipp::IwException ex)
        {
            CHECK_STATUS_PRINT_AC(ex.m_status, "catch(IwException)", iwGetStatusString(ex), return STS_ERR_FAILED);
        }

        return STS_OK;
    }

public:
    Image               m_inter[5]; // Array of intermediate buffers
    AutoBuffer<Ipp8u>   m_sharpBuffer;
};

int main(int argc, char *argv[])
{
#ifdef _CRTDBG_MAP_ALLOC
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
#endif

    /*
    // Variables initialization
    */
    Status       status         = STS_OK;
    DString      sInputFile     = CheckTestDirs( BMP_RGB_FILE );
    DString      sIppCpu;

    int           tileSizeP[2]   = {-1, -1};
    bool          bNoWindow      = false;
    bool          bPrintHelp     = false;

    TilingDemoRef pipeRef;
    TilingDemo    pipeDemo;

    Image srcData;
    Image dstData;
    Image dstRef;

    /*
    // Cmd parsing
    */
    const cmd::OptDef cmdOpts[] = {
        { 'i', "", 1, cmd::KT_DSTRING,   cmd::KF_OPTIONAL,  &sInputFile,      "input file name" },
        { 'b', "", 2, cmd::KT_INTEGER,   0,                 &tileSizeP[0],    "tile size for processing"},
#ifdef ENABLE_RENDERING
        { 's', "", 1, cmd::KT_BOOL,      0,                 &bNoWindow,       "suppress window output" },
#endif
        { 'T', "", 1, cmd::KT_DSTRING,   0,                 &sIppCpu,         "target Intel(R) IPP optimization (" IPP_OPT_LIST ")" },
        { 'h', "", 1, cmd::KT_BOOL,      0,                 &bPrintHelp,      "print help and exit" },
        {0}
    };

    if(cmd::OptParse(argc, argv, cmdOpts))
    {
        printHelp(cmdOpts, argv);
        PRINT_MESSAGE("invalid input parameters");
        return 1;
    }

    InitPreferredCpu(sIppCpu.c_str());

    printVersion();

    // Check default image availability
    if ( !strcmp(sInputFile.c_str(), BMP_GRAYSCALE_FILE) ) {
        bPrintHelp = ( -1 == vm_file_access(sInputFile.c_str(), 0) );
    }

    if(bPrintHelp)
    {
        printHelp(cmdOpts, argv);
        return 0;
    }

    if(!sInputFile.Size())
    {
        printHelp(cmdOpts, argv);
        PRINT_MESSAGE("Cannot open input file");
        return 1;
    }

    for(;;)
    {
        Size                tileSize(tileSizeP[0], tileSizeP[1]);
        std::vector<Rect>   rects;
        std::vector<Image>  buffers;

        // Read from file
        printf("\nInput file: %s\n", sInputFile.c_str());
        status = srcData.Read(sInputFile);
        CHECK_STATUS_PRINT_BR(status, "Image::Read()", GetBaseStatusString(status));

        // Scale down input image a little, for better view
        {
            Image srcScaled;
            srcScaled.Alloc(Size(srcData.m_size.width/4, srcData.m_size.height/4), srcData.m_color, srcData.m_sampleFormat);
            ipp::IwiImage iwSrc = ImageToIwImage(srcData);
            ipp::IwiImage iwDst = ImageToIwImage(srcScaled);
            ipp::iwiResize(iwSrc, iwDst, ippNearest);
            srcData = srcScaled;
        }

        status = TilingBase::InitExternal(srcData, dstData);
        CHECK_STATUS_PRINT_BR(status, "TilingDemo::InitExternal()", GetBaseStatusString(status));

        printf("Input info: %dx%d %s\n\n", (int)srcData.m_size.width, (int)srcData.m_size.height, colorFormatName[srcData.m_color]);
        printf("Output info: %dx%d %s\n\n", (int)dstData.m_size.width, (int)dstData.m_size.height, colorFormatName[dstData.m_color]);

        tileSize = tileSetDefaults(tileSize, dstData.m_size);

        status = pipeDemo.Init(srcData, dstData, tileSize);
        CHECK_STATUS_PRINT_BR(status, "TilingDemo::InitExternal()", GetBaseStatusString(status));

        status = pipeRef.Init(srcData, dstRef);
        CHECK_STATUS_PRINT_BR(status, "TilingDemoRef::Init()", GetBaseStatusString(status));

        printf("Tile: %dx%d\n", (int)tileSize.width, (int)tileSize.height);

        WindowDraw draw;
        if(!bNoWindow)
        {
            draw.Create("Intel(R) IPP IW Pipeline Tiling Demo");
            if(draw.IsInitialized())
            {
                printf("\nPress Space to progress through tiling\n");
                printf("\nClose window to stop rendering and finish\n");
            }
        }

        status = pipeRef.Run(srcData, dstRef);
        CHECK_STATUS_PRINT_BR(status, "TilingRef::Run()", GetBaseStatusString(status));

        dstData.Set(-MAX_DOUBLE_ABS);

        {
            for(long long row = 0; row < dstData.m_size.height; row += tileSize.height)
            {
                for(long long col = 0; col < dstData.m_size.width; col += tileSize.width)
                {
                    rects.clear();
                    buffers.clear();

                    status = pipeDemo.Run(srcData, dstData, Rect(col, row, tileSize.width, tileSize.height));
                    CHECK_STATUS_PRINT_BR(status, "TilingDemo::Run()", GetBaseStatusString(status));

                    pipeDemo.GetRects(rects);
                    buffers.push_back(srcData);
                    pipeDemo.GetBuffers(buffers);
                    buffers.push_back(dstData);

                    Draw(draw, buffers, rects, true);
                }
                if(status < 0)
                    break;
            }
        }

        /*
        // Results output
        */
        std::vector<double> diff = dstData.FindMaxDiff(dstRef);
        if(diff.size())
            printf("Max diff:   %.4f\n", diff[0]);

        break;
    }

    if(status < 0)
        return status;
    return 0;
}
