/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_CTL_GFX_COLOR_FX_2D_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_CTL_GFX_COLOR_FX_2D_TPP


#ifndef ECXX_CTL_GFX_COLOR_FX_2D_TPP
#define ECXX_CTL_GFX_COLOR_FX_2D_TPP


namespace eCxx {


//
// Helper structure for ColorFX2D<T> implementation
//
template <typename ColorFX2D_T, typename BufferT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN>
struct ColorFX2D_CMHelper {
    // Get a vertical strip of colors
    template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
    static inline void getVStrip(
        const ColorFX2D_T*                                                    cfx,
        const BufferT                                                         /* buffer */,
        CBP::DSInt                                                            x,
        CBP::DSInt                                                            y,
        ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip
    )
    {
        // Clear the destination first
        strip.clear();

        // Check the source X index
        if(x < 0 || x >= cfx->XElementCount) return;

        // Source index
        CBP::SUInt di = 0;

        // Loop the source Y elements
        for(CBP::DSInt sy = y; sy < y + strip.BufferElementCount; ++sy) {
            // Continue only if the source Y index is in range
            if(sy >= 0 && sy < cfx->YElementCount) {
                // Copy source color to destination color
                strip.set( di, cfx->getXY(x, sy) );
            }
            // Increment the destination index
            ++di;
        }
    }

    // Get a horizontal strip of colors
    template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
    static inline void getHStrip(
        const ColorFX2D_T*                                                    cfx,
        const BufferT                                                         /* buffer */,
        CBP::DSInt                                                            x,
        CBP::DSInt                                                            y,
        ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip
    )
    {
        // Clear the destination first
        strip.clear();

        // Check the source Y index
        if(y < 0 || y >= cfx->YElementCount) return;

        // Source index
        CBP::SUInt di = 0;

        // Loop the source X elements
        for(CBP::DSInt sx = x; sx < x + strip.BufferElementCount; ++sx) {
            // Continue only if the source X index is in range
            if(sx >= 0 && sx < cfx->XElementCount) {
                // Copy source color to destination color
                strip.set( di, cfx->getXY(sx, y) );
            }
            // Increment the destination index
            ++di;
        }
    }

    // Put a vertical strip of colors
    template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
    static inline void putVStrip(
        ColorFX2D_T*                                                              cfx,
        BufferT                                                                   /* buffer */,
        CBP::DSInt                                                                x,
        CBP::DSInt                                                                y,
        const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip
    )
    {
        // Check the destination X index
        if(x < 0 || x >= cfx->XElementCount) return;

        // Source index
        CBP::SUInt si = 0;

        // Loop the destination Y elements
        for(CBP::DSInt dy = y; dy < y + strip.BufferElementCount; ++dy) {
            // Stop if the destination Y index already passes the maximum Y coordinate
            if(dy >= cfx->YElementCount) break;
            // Continue only if the destination Y index is not negative
            if(dy >= 0) {
                // Copy source color to destination color
                cfx->setXY( x, dy, strip.get(si) );
            }
            // Increment the source index
            ++si;
        }
    }

    // Put a horizontal strip of colors
    template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
    static inline void putHStrip(
        ColorFX2D_T*                                                              cfx,
        BufferT                                                                   /* buffer */,
        CBP::DSInt                                                                x,
        CBP::DSInt                                                                y,
        const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip
    )
    {
        // Check the destination Y index
        if(y < 0 || y >= cfx->YElementCount) return;

        // Source index
        CBP::SUInt si = 0;

        // Loop the destination X elements
        for(CBP::DSInt dx = x; dx < x + strip.BufferElementCount; ++dx) {
            // Stop if the destination X index already passes the maximum X coordinate
            if(dx >= cfx->XElementCount) break;
            // Continue only if the destination X index is not negative
            if(dx >= 0) {
                // Copy source color to destination color
                cfx->setXY( dx, y, strip.get(si) );
            }
            // Increment the source index
            ++si;
        }
    }

    // Get a block of colors
    template <typename BufferColorT_, CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_>
    static inline void getXY(
        const ColorFX2D_T*                                                          cfx,
        const BufferT                                                               /* buffer */,
        CBP::DSInt                                                                  x,
        CBP::DSInt                                                                  y,
        ColorFX2D<BufferColorT_, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_>& block
    )
    {
        // Clear the destination first
        block.clear();

        // Destination indexes
        CBP::SUInt dx = 0;
        CBP::SUInt dy = 0;

        // Loop the source Y elements
        for(CBP::DSInt sy = y; sy < y + block.YElementCount; ++sy) {
            // Stop if the source Y index already passes the maximum Y coordinate
            if(sy >= cfx->YElementCount) break;
            // Continue only if the source Y index is not negative
            if(sy >= 0) {
                // Reset the destination X index
                dx = 0;
                // Loop the source X elements
                for(CBP::DSInt sx = x; sx < x + block.XElementCount; ++sx) {
                    // Stop if the source X index already passes the maximum X coordinate
                    if(sx >= cfx->XElementCount) break;
                    // Continue only if the source X index is not negative
                    if(sx >= 0) {
                        // Copy source color to destination color
                        block.setXY( dx, dy, cfx->getXY(sx, sy) );
                    }
                    // Increment the destination X index
                    ++dx;
                }
            }
            // Increment the destination Y index
            ++dy;
        }
    }

    // Put a block of colors
    template <typename BufferColorT_, CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_>
    static inline void putXY(
        ColorFX2D_T*                                                                      cfx,
        BufferT                                                                           /* buffer */,
        CBP::DSInt                                                                        x,
        CBP::DSInt                                                                        y,
        const ColorFX2D<BufferColorT_, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_>& block
    )
    {
        // Source indexes
        CBP::SUInt sx = 0;
        CBP::SUInt sy = 0;

        // Loop the destination Y elements
        for(CBP::DSInt dy = y; dy < y + block.YElementCount; ++dy) {
            // Stop if the destination Y index already passes the maximum Y coordinate
            if(dy >= cfx->YElementCount) break;
            // Continue only if the destination Y index is not negative
            if(dy >= 0) {
                // Reset the source X index
                sx = 0;
                // Loop the destination X elements
                for(CBP::DSInt dx = x; dx < x + block.XElementCount; ++dx) {
                    // Stop if the destination X index already passes the maximum X coordinate
                    if(dx >= cfx->XElementCount) break;
                    // Continue only if the destination X index is not negative
                    if(dx >= 0) {
                        // Copy source color to destination color
                        cfx->setXY( dx, dy, block.getXY(sx, sy) );
                    }
                    // Increment the source X index
                    ++sx;
                }
            }
            // Increment the source Y index
            ++sy;
        }
    }

    // Put a sprite from a sprite sheet
    template <typename ColorT, CBP::SUInt SpriteXCountN, CBP::SUInt SpriteYCountN, CBP::SUInt SpriteXSizeN, CBP::SUInt SpriteYSizeN, typename SSPGMPointerT>
    static inline void putSpriteXY(
        ColorFX2D_T*                                                                                          cfx,
        BufferT                                                                                               /* buffer */,
        CBP::DSInt                                                                                            x,
        CBP::DSInt                                                                                            y,
        const SpriteSheet2D<ColorT, SpriteXCountN, SpriteYCountN, SpriteXSizeN, SpriteYSizeN, SSPGMPointerT>& ss,
        CBP::DSInt                                                                                            xIndex,
        CBP::SUInt                                                                                            yIndex,
        const ColorT*                                                                                         transCol = nullptr
    )
    {
        // Begin obtaining the sprite color data
        ss.obtainSCDBegin(xIndex, yIndex);

        // Loop the destination Y elements
        for(CBP::DSInt dy = y; dy < y + ss.YSize; ++dy) {
            // Loop the destination X elements
            for(CBP::DSInt dx = x; dx < x + ss.XSize; ++dx) {
                // Get the sprite color data
                const ColorT& col = ss.obtainSCDGet();
                // Check the destination coordinates
                if(dx <  0                 ) continue;
                if(dx >= cfx->XElementCount) continue;
                if(dy <  0                 ) continue;
                if(dy >= cfx->YElementCount) return;
                // Skip the transparent color
                if(transCol && *transCol == col) continue;
                // Copy sprite color to destination color
                cfx->setXY(dx, dy, col);
            }
        }
    }

    // Generate vertical gradient by using linear interpolation between two strips
    template < uint8_t    ChannelIndexes,
               typename   BufferColorT_,
               CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
               CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
    static inline void lerpGV(
        ColorFX2D_T*                                                                   cfx,
        BufferT                                                                        /* buffer */,
        CBP::SUInt                                                                     xFrom,
        const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
        CBP::DUInt                                                                     ofsFrom,
        CBP::SUInt                                                                     xTo,
        const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
        CBP::DUInt                                                                     ofsTo,
        CBP::SUInt                                                                     y1,
        CBP::SUInt                                                                     y2
    )
    {
        // Source index
        CBP::SUInt sy = 0;

        // Loop the destination Y elements
        for(CBP::DSInt dy = y1; dy <= y2; ++dy) {
            // Get the source colors and increment the source index
            const BufferColorT_& fCol = stripFrom.get(ofsFrom + sy);
            const BufferColorT_& tCol = stripTo  .get(ofsTo   + sy);
            ++sy;
            // Loop the destination X elements
            for(CBP::DSInt dx = xFrom; dx <= xTo; ++dx) {
                // Perform linear interpolation
                BufferColorT_ mCol;
                lerpColor<ChannelIndexes>(mCol, fCol, tCol, dx - xFrom, xTo - xFrom);
                // Set the interpolated color as the destination color
                cfx->setXY(dx, dy, mCol);
            }
        }
    }

    // Generate horizontal gradient by using linear interpolation between two strips
    template < uint8_t    ChannelIndexes,
               typename   BufferColorT_,
               CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
               CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_ >
    static inline void lerpGH(
        ColorFX2D_T*                                                                   cfx,
        BufferT                                                                        /* buffer */,
        CBP::SUInt                                                                     yFrom,
        const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
        CBP::DUInt                                                                     ofsFrom,
        CBP::SUInt                                                                     yTo,
        const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
        CBP::DUInt                                                                     ofsTo,
        CBP::SUInt                                                                     x1,
        CBP::SUInt                                                                     x2
    )
    {
        // Source index
        CBP::SUInt sx = 0;

        // Loop the destination X elements
        for(CBP::DSInt dx = x1; dx <= x2; ++dx) {
            // Get the source colors and increment the source index
            const BufferColorT_& fCol = stripFrom.get(ofsFrom + sx);
            const BufferColorT_& tCol = stripTo  .get(ofsTo   + sx);
            ++sx;
            // Loop the destination Y elements
            for(CBP::DSInt dy = yFrom; dy <= yTo; ++dy) {
                // Perform linear interpolation
                BufferColorT_ mCol;
                lerpColor<ChannelIndexes>(mCol, fCol, tCol, dy - yFrom, yTo - yFrom);
                // Set the interpolated color as the destination color
                cfx->setXY(dx, dy, mCol);
            }
        }
    }
};


//
// A simple 2-dimensional color array and effect class
//

// Get one color
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline const typename ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>::BufferColorType ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::getXY(CBP::SUInt x, CBP::SUInt y) const
{ return _CFX1D::get( _CBLP::_(x, y) ); }


// Set one color
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename ColorT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::setXY(CBP::SUInt x, CBP::SUInt y, const ColorT& color)
{ _CFX1D::set( _CBLP::_(x, y), color ); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename ColorT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::setXY(CBP::SUInt x, CBP::SUInt y, ColorPGMPointerType color)
{ _CFX1D::set( _CBLP::_(x, y), color ); }


// Set all colors from program memory
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename ColorT, typename ColorPGMPtrT_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::setXY(ColorPGMPtrT_ colors)
{
    ECXX_CHECK_IF_TEMPLATE_ARG_IS_PGM_CHAR_POINTER(ColorPGMPtrT_);

    // Source indexes
    auto pgmPtr = colors.p;

    // Loop the destination Y elements
    for(CBP::SUInt y = 0; y < YElementCount; ++y) {
        // Loop the destination X elements
        for(CBP::SUInt x = 0; x < XElementCount; ++x) {
            // Set color
             _CFX1D::set( _CBLP::_(x, y), ColorT::fromPGMCharPointerRaw(pgmPtr) );
            // Increment the source pointer
            pgmPtr += ColorT::RawByteSize;
        }
    }
}


// Get a strip of colors
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::getVStrip(CBP::DSInt x, CBP::DSInt y, ColorFX1D<BufferColorType, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip)
{ _CMHelper::getVStrip(this, this->begin(), x, y, strip); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::getHStrip(CBP::DSInt x, CBP::DSInt y, ColorFX1D<BufferColorType, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip)
{ _CMHelper::getHStrip(this, this->begin(), x, y, strip); }


// Put a strip of colors
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::putVStrip(CBP::DSInt x, CBP::DSInt y, const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip)
{ _CMHelper::putVStrip(this, this->begin(), x, y, strip); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename BufferColorT_, CBP::DUInt BufferSizeN_, typename BufferPolicyT_, typename BufferSizeT_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::putHStrip(CBP::DSInt x, CBP::DSInt y, const ColorFX1D<BufferColorT_, BufferSizeN_, BufferPolicyT_, BufferSizeT_>& strip)
{ _CMHelper::putHStrip(this, this->begin(), x, y, strip); }


// Get a block of colors
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_, CBP::SUInt BlockXSizeN_, CBP::SUInt BlockYSizeN_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::getXY(CBP::DSInt x, CBP::DSInt y, ColorFX2D<BufferColorType, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_, BlockXSizeN_, BlockYSizeN_>& block)
{ _CMHelper::getXY(this, this->begin(), x, y, block); }


// Put a block of colors
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename BufferColorT_, CBP::SUInt XSizeN_, CBP::SUInt YSizeN_, typename LayoutPolicyT_, typename BufferPolicyT_, CBP::SUInt BlockXSizeN_, CBP::SUInt BlockYSizeN_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::putXY(CBP::DSInt x, CBP::DSInt y, const ColorFX2D<BufferColorT_, XSizeN_, YSizeN_, LayoutPolicyT_, BufferPolicyT_, BlockXSizeN_, BlockYSizeN_>& block)
{ _CMHelper::putXY(this, this->begin(), x, y, block); }


// Put a sprite
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename ColorT, CBP::SUInt SpriteXCountN, CBP::SUInt SpriteYCountN, CBP::SUInt SpriteXSizeN, CBP::SUInt SpriteYSizeN, typename SSPGMPointerT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::putSpriteXY(CBP::DSInt x, CBP::DSInt y, const SpriteSheet2D<ColorT, SpriteXCountN, SpriteYCountN, SpriteXSizeN, SpriteYSizeN, SSPGMPointerT>& ss, CBP::DSInt xIndex, CBP::SUInt yIndex)
{ _CMHelper::putSpriteXY(this, this->begin(), x, y, ss, xIndex, yIndex); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename ColorT, CBP::SUInt SpriteXCountN, CBP::SUInt SpriteYCountN, CBP::SUInt SpriteXSizeN, CBP::SUInt SpriteYSizeN, typename SSPGMPointerT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::putSpriteXY(CBP::DSInt x, CBP::DSInt y, const SpriteSheet2D<ColorT, SpriteXCountN, SpriteYCountN, SpriteXSizeN, SpriteYSizeN, SSPGMPointerT>& ss, CBP::DSInt xIndex, CBP::SUInt yIndex, const ColorT& transparentColor)
{ _CMHelper::putSpriteXY(this, this->begin(), x, y, ss, xIndex, yIndex, &transparentColor); }


// Generate vertical gradient by using linear interpolation between two strips
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <uint8_t    ChannelIndexes,
          typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGV(
    CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
    CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
    CBP::SUInt y1,
    CBP::SUInt y2
)
{ _CMHelper::template lerpGV<ChannelIndexes>(this, this->begin(), xFrom, stripFrom, ofsFrom, xTo, stripTo, ofsTo, y1, y2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <uint8_t    ChannelIndexes,
          typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGV(
    CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
    CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
    CBP::SUInt y1,
    CBP::SUInt y2
)
{ lerpGV<ChannelIndexes>(xFrom, stripFrom, 0, xTo, stripTo, 0, y1, y2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGV(
    CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
    CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
    CBP::SUInt y1,
    CBP::SUInt y2
)
{ lerpGV<BufferColorT_::A>(xFrom, stripFrom, ofsFrom, xTo, stripTo, ofsTo, y1, y2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGV(
    CBP::SUInt xFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
    CBP::SUInt xTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
    CBP::SUInt y1,
    CBP::SUInt y2
)
{ lerpGV<BufferColorT_::A>(xFrom, stripFrom, 0, xTo, stripTo, 0, y1, y2); }


// Generate horizontal gradient by using linear interpolation between two strips
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <uint8_t    ChannelIndexes,
          typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGH(
    CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
    CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
    CBP::SUInt x1,
    CBP::SUInt x2
)
{ _CMHelper::template lerpGH<ChannelIndexes>(this, this->begin(), yFrom, stripFrom, ofsFrom, yTo, stripTo, ofsTo, x1, x2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <uint8_t    ChannelIndexes,
          typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGH(
    CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
    CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
    CBP::SUInt x1,
    CBP::SUInt x2
)
{ lerpGH<ChannelIndexes>(yFrom, stripFrom, 0, yTo, stripTo, 0, x1, x2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGH(
    CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom, CBP::DUInt ofsFrom,
    CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,   CBP::DUInt ofsTo,
    CBP::SUInt x1,
    CBP::SUInt x2
)
{ lerpGH<BufferColorT_::A>(yFrom, stripFrom, ofsFrom, yTo, stripTo, ofsTo, x1, x2); }


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
template <typename   BufferColorT_,
          CBP::DUInt BufferSize1N_, typename BufferPolicy1T_, typename BufferSize1T_,
          CBP::DUInt BufferSize2N_, typename BufferPolicy2T_, typename BufferSize2T_>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::lerpGH(
    CBP::SUInt yFrom, const ColorFX1D<BufferColorT_, BufferSize1N_, BufferPolicy1T_, BufferSize1T_>& stripFrom,
    CBP::SUInt yTo,   const ColorFX1D<BufferColorT_, BufferSize2N_, BufferPolicy2T_, BufferSize2T_>& stripTo,
    CBP::SUInt x1,
    CBP::SUInt x2
)
{ lerpGH<BufferColorT_::A>(yFrom, stripFrom, 0, yTo, stripTo, 0, x1, x2); }


// Flip
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::flipHorizontal()
{
    for(CBP::SUInt y = 0; y < YElementCount; ++y) {
        for(CBP::SUInt x = 0; x < XElementCount / 2; ++x) {
            const BufferColorType& c0 = getXY(                    x, y);
            const BufferColorType& c1 = getXY(XElementCount - 1 - x, y);
            setXY(                    x, y, c1);
            setXY(XElementCount - 1 - x, y, c0);
        }
    }
}


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::flipVertical()
{
    for(CBP::SUInt y = 0; y < YElementCount / 2; ++y) {
        for(CBP::SUInt x = 0; x < XElementCount; ++x) {
            const BufferColorType& c0 = getXY(x,                     y);
            const BufferColorType& c1 = getXY(x, YElementCount - 1 - y);
            setXY(x,                     y, c1);
            setXY(x, YElementCount - 1 - y, c0);
        }
    }
}


// Rotate
template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::_transpose()
{
    static_assert(XSizeN == YSizeN, "template argument substituting XSizeN and YSizeN are not equal");

    for(CBP::SUInt i = 0; i < XElementCount - 1; ++i) {
        for(CBP::SUInt j = i + 1; j < XElementCount; ++j) {
            const BufferColorType& c0 = getXY(i, j);
            const BufferColorType& c1 = getXY(j, i);
            setXY(i, j, c1);
            setXY(j, i, c0);
        }
    }
}


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::rotate90CW()
{
    _transpose();
    flipHorizontal();
}


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::rotate90CCW()
{
    _transpose();
    flipVertical();
}


template <typename BufferColorT, CBP::SUInt XSizeN, CBP::SUInt YSizeN, typename LayoutPolicyT, typename BufferPolicyT, CBP::SUInt BlockXSizeN, CBP::SUInt BlockYSizeN, CBP::SUInt InterYSizeN, bool UseDoubleBufferring, typename ColorPGMPtrT>
inline void ColorFX2D<BufferColorT, XSizeN, YSizeN, LayoutPolicyT, BufferPolicyT, BlockXSizeN, BlockYSizeN, InterYSizeN, UseDoubleBufferring, ColorPGMPtrT>
::rotate180()
{
    flipHorizontal();
    flipVertical();
}


} // namespace eCxx


#endif
