/*****************************************************************************/
// Copyright 2006-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

#include "dng_pixel_buffer.h"

#include "dng_bottlenecks.h"
#include "dng_exceptions.h"
#include "dng_flags.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"

/*****************************************************************************/

static bool SafeUint32ToInt32Mult (uint32 arg1,
                                   uint32 arg2,
                                   int32 *result)
    {

    uint32 uint32_result;

    return (SafeUint32Mult (arg1,
                            arg2,
                            &uint32_result) &&

            ConvertUint32ToInt32 (uint32_result,
                                  result));

    }

/*****************************************************************************/

void OptimizeOrder (const void *&sPtr,
                    void *&dPtr,
                    uint32 sPixelSize,
                    uint32 dPixelSize,
                    uint32 &count0,
                    uint32 &count1,
                    uint32 &count2,
                    int32 &sStep0,
                    int32 &sStep1,
                    int32 &sStep2,
                    int32 &dStep0,
                    int32 &dStep1,
                    int32 &dStep2)
    {

    uint32 step0;
    uint32 step1;
    uint32 step2;

    // Optimize the order for the data that is most spread out.

    uint32 sRange = Abs_int32 (sStep0) * (count0 - 1) +
                    Abs_int32 (sStep1) * (count1 - 1) +
                    Abs_int32 (sStep2) * (count2 - 1);

    uint32 dRange = Abs_int32 (dStep0) * (count0 - 1) +
                    Abs_int32 (dStep1) * (count1 - 1) +
                    Abs_int32 (dStep2) * (count2 - 1);

    if (dRange >= sRange)
        {

        if (dStep0 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count0 - 1) * sStep0 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count0 - 1) * dStep0 * (int32)dPixelSize);

            sStep0 = -sStep0;
            dStep0 = -dStep0;

            }

        if (dStep1 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count1 - 1) * sStep1 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count1 - 1) * dStep1 * (int32)dPixelSize);

            sStep1 = -sStep1;
            dStep1 = -dStep1;

            }

        if (dStep2 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count2 - 1) * sStep2 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count2 - 1) * dStep2 * (int32)dPixelSize);

            sStep2 = -sStep2;
            dStep2 = -dStep2;

            }

        step0 = (uint32) dStep0;
        step1 = (uint32) dStep1;
        step2 = (uint32) dStep2;

        }

    else
        {

        if (sStep0 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count0 - 1) * sStep0 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count0 - 1) * dStep0 * (int32)dPixelSize);

            sStep0 = -sStep0;
            dStep0 = -dStep0;

            }

        if (sStep1 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count1 - 1) * sStep1 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count1 - 1) * dStep1 * (int32)dPixelSize);

            sStep1 = -sStep1;
            dStep1 = -dStep1;

            }

        if (sStep2 < 0)
            {

            sPtr = (const void *)
                   (((const uint8 *) sPtr) + (int32)(count2 - 1) * sStep2 * (int32)sPixelSize);

            dPtr = (void *)
                   (((uint8 *) dPtr) + (int32)(count2 - 1) * dStep2 * (int32)dPixelSize);

            sStep2 = -sStep2;
            dStep2 = -dStep2;

            }

        step0 = (uint32) sStep0;
        step1 = (uint32) sStep1;
        step2 = (uint32) sStep2;

        }

    if (count0 == 1) step0 = 0xFFFFFFFF;
    if (count1 == 1) step1 = 0xFFFFFFFF;
    if (count2 == 1) step2 = 0xFFFFFFFF;

    uint32 index0;
    uint32 index1;
    uint32 index2;

    if (step0 >= step1)
        {

        if (step1 >= step2)
            {
            index0 = 0;
            index1 = 1;
            index2 = 2;
            }

        else if (step2 >= step0)
            {
            index0 = 2;
            index1 = 0;
            index2 = 1;
            }

        else
            {
            index0 = 0;
            index1 = 2;
            index2 = 1;
            }

        }

    else
        {

        if (step0 >= step2)
            {
            index0 = 1;
            index1 = 0;
            index2 = 2;
            }

        else if (step2 >= step1)
            {
            index0 = 2;
            index1 = 1;
            index2 = 0;
            }

        else
            {
            index0 = 1;
            index1 = 2;
            index2 = 0;
            }

        }

    uint32 count [3];

    count [0] = count0;
    count [1] = count1;
    count [2] = count2;

    count0 = count [index0];
    count1 = count [index1];
    count2 = count [index2];

    int32 step [3];

    step [0] = sStep0;
    step [1] = sStep1;
    step [2] = sStep2;

    sStep0 = step [index0];
    sStep1 = step [index1];
    sStep2 = step [index2];

    step [0] = dStep0;
    step [1] = dStep1;
    step [2] = dStep2;

    dStep0 = step [index0];
    dStep1 = step [index1];
    dStep2 = step [index2];

    if (sStep0 == ((int32) count1) * sStep1 &&
        dStep0 == ((int32) count1) * dStep1)
        {
        count1 *= count0;
        count0 = 1;
        }

    if (sStep1 == ((int32) count2) * sStep2 &&
        dStep1 == ((int32) count2) * dStep2)
        {
        count2 *= count1;
        count1 = 1;
        }

    }

/*****************************************************************************/

void OptimizeOrder (const void *&sPtr,
                    uint32 sPixelSize,
                    uint32 &count0,
                    uint32 &count1,
                    uint32 &count2,
                    int32 &sStep0,
                    int32 &sStep1,
                    int32 &sStep2)
    {

    void *dPtr = NULL;

    int32 dStep0 = sStep0;
    int32 dStep1 = sStep1;
    int32 dStep2 = sStep2;

    OptimizeOrder (sPtr,
                   dPtr,
                   sPixelSize,
                   sPixelSize,
                   count0,
                   count1,
                   count2,
                   sStep0,
                   sStep1,
                   sStep2,
                   dStep0,
                   dStep1,
                   dStep2);

    }

/*****************************************************************************/

void OptimizeOrder (void *&dPtr,
                    uint32 dPixelSize,
                    uint32 &count0,
                    uint32 &count1,
                    uint32 &count2,
                    int32 &dStep0,
                    int32 &dStep1,
                    int32 &dStep2)
    {

    const void *sPtr = NULL;

    int32 sStep0 = dStep0;
    int32 sStep1 = dStep1;
    int32 sStep2 = dStep2;

    OptimizeOrder (sPtr,
                   dPtr,
                   dPixelSize,
                   dPixelSize,
                   count0,
                   count1,
                   count2,
                   sStep0,
                   sStep1,
                   sStep2,
                   dStep0,
                   dStep1,
                   dStep2);

    }

/*****************************************************************************/

dng_pixel_buffer::dng_pixel_buffer ()

    :   fArea       ()
    ,   fPlane      (0)
    ,   fPlanes     (1)
    ,   fRowStep    (1)
    ,   fColStep    (1)
    ,   fPlaneStep  (1)
    ,   fPixelType  (ttUndefined)
    ,   fPixelSize  (0)
    ,   fData       (NULL)
    ,   fDirty      (true)

    {

    }

/*****************************************************************************/

dng_pixel_buffer::dng_pixel_buffer (const dng_rect &area,
                                    uint32 plane,
                                    uint32 planes,
                                    uint32 pixelType,
                                    uint32 planarConfiguration,
                                    void *data)

    :   fArea       (area)
    ,   fPlane      (plane)
    ,   fPlanes     (planes)
    ,   fRowStep    (0)
    ,   fColStep    (0)
    ,   fPlaneStep  (0)
    ,   fPixelType  (pixelType)
    ,   fPixelSize  (TagTypeSize (pixelType))
    ,   fData       (data)
    ,   fDirty      (true)

    {

    const char *overflowMessage = "Arithmetic overflow in pixel buffer setup";

    // Initialize fRowStep, fColStep and fPlaneStep according to the desired
    // pixel layout.

    switch (planarConfiguration)
        {

        case pcInterleaved:
            {

            fPlaneStep = 1;

            if (!ConvertUint32ToInt32 (fPlanes, &fColStep) ||
                !SafeUint32ToInt32Mult (fArea.W (), fPlanes, &fRowStep))
                {
                ThrowOverflow (overflowMessage);
                }

            break;

            }

        case pcPlanar:
            {

            fColStep = 1;

            // Even though we've hardened dng_rect::W() to guarantee that it
            // will never return a result that's out of range for an int32, we
            // still protect the conversion for defense in depth.

            if (!ConvertUint32ToInt32 (fArea.W (), &fRowStep) ||
                !SafeUint32ToInt32Mult (fArea.H (), fArea.W (), &fPlaneStep))
                {
                ThrowOverflow (overflowMessage);
                }

            break;

            }

        case pcRowInterleaved:
        case pcRowInterleavedAlignSIMD:
            {

            fColStep = 1;

            uint32 planeStepUint32;

            if (planarConfiguration == pcRowInterleaved)
                {
                planeStepUint32 = fArea.W ();
                }

            else
                {

                if (!RoundUpForPixelSize (fArea.W (),
                                          fPixelSize,
                                          &planeStepUint32))
                    {
                    ThrowOverflow (overflowMessage);
                    }

                }

            if (!ConvertUint32ToInt32  (planeStepUint32, &fPlaneStep) ||
                !SafeUint32ToInt32Mult (planeStepUint32, fPlanes, &fRowStep))
                {
                ThrowOverflow (overflowMessage);
                }

            break;

            }

        default:
            {
            ThrowProgramError ("Invalid value for 'planarConfiguration'");
            break;
            }

        }

    }

/*****************************************************************************/

dng_pixel_buffer::dng_pixel_buffer (const dng_pixel_buffer &buffer)

    :   fArea       (buffer.fArea)
    ,   fPlane      (buffer.fPlane)
    ,   fPlanes     (buffer.fPlanes)
    ,   fRowStep    (buffer.fRowStep)
    ,   fColStep    (buffer.fColStep)
    ,   fPlaneStep  (buffer.fPlaneStep)
    ,   fPixelType  (buffer.fPixelType)
    ,   fPixelSize  (buffer.fPixelSize)
    ,   fData       (buffer.fData)
    ,   fDirty      (buffer.fDirty)

    {

    }

/*****************************************************************************/

dng_pixel_buffer & dng_pixel_buffer::operator= (const dng_pixel_buffer &buffer)
    {

    fArea       = buffer.fArea;
    fPlane      = buffer.fPlane;
    fPlanes     = buffer.fPlanes;
    fRowStep    = buffer.fRowStep;
    fColStep    = buffer.fColStep;
    fPlaneStep  = buffer.fPlaneStep;
    fPixelType  = buffer.fPixelType;
    fPixelSize  = buffer.fPixelSize;
    fPixelType  = buffer.fPixelType;
    fData       = buffer.fData;
    fDirty      = buffer.fDirty;

    return *this;

    }

/*****************************************************************************/

dng_pixel_buffer::~dng_pixel_buffer ()
    {

    }

/*****************************************************************************/

#if qDebugPixelType

void dng_pixel_buffer::CheckPixelType (uint32 pixelType) const
    {

    if (fPixelType != pixelType)
        {

        DNG_REPORT ("Pixel type access mismatch");

        }

    }

#endif

/*****************************************************************************/

uint32 dng_pixel_buffer::PixelRange () const
    {

    switch (fPixelType)
        {

        case ttByte:
        case ttSByte:
            {
            return 0x0FF;
            }

        case ttShort:
        case ttSShort:
            {
            return 0x0FFFF;
            }

        case ttLong:
        case ttSLong:
            {
            return 0xFFFFFFFF;
            }

        default:
            break;

        }

    return 0;

    }

/*****************************************************************************/

void dng_pixel_buffer::SetConstant (const dng_rect &area,
                                    uint32 plane,
                                    uint32 planes,
                                    uint32 value)
    {

    uint32 rows = area.H ();
    uint32 cols = area.W ();

    void *dPtr = DirtyPixel (area.t,
                             area.l,
                             plane);

    int32 dRowStep   = fRowStep;
    int32 dColStep   = fColStep;
    int32 dPlaneStep = fPlaneStep;

    OptimizeOrder (dPtr,
                   fPixelSize,
                   rows,
                   cols,
                   planes,
                   dRowStep,
                   dColStep,
                   dPlaneStep);

    switch (fPixelSize)
        {

        case 1:
            {

            if (rows == 1 && cols == 1 && dPlaneStep == 1 && value == 0)
                {

                DoZeroBytes (dPtr, planes);

                }

            else
                {

                DoSetArea8 ((uint8 *) dPtr,
                            (uint8) value,
                            rows,
                            cols,
                            planes,
                            dRowStep,
                            dColStep,
                            dPlaneStep);

                }

            break;

            }

        case 2:
            {

            if (rows == 1 && cols == 1 && dPlaneStep == 1 && value == 0)
                {

                DoZeroBytes (dPtr, planes << 1);

                }

            else
                {

                DoSetArea16 ((uint16 *) dPtr,
                             (uint16) value,
                             rows,
                             cols,
                             planes,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                }

            break;

            }

        case 4:
            {

            if (rows == 1 && cols == 1 && dPlaneStep == 1 && value == 0)
                {

                DoZeroBytes (dPtr, planes << 2);

                }

            else
                {

                DoSetArea32 ((uint32 *) dPtr,
                             value,
                             rows,
                             cols,
                             planes,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                }

            break;

            }

        default:
            {

            ThrowNotYetImplemented ();

            }

        }

    }

/*****************************************************************************/

void dng_pixel_buffer::SetZero (const dng_rect &area,
                                uint32 plane,
                                uint32 planes)
    {

    uint32 value = 0;

    switch (fPixelType)
        {

        case ttByte:
        case ttShort:
        case ttLong:
        case ttFloat:
            {
            break;
            }

        case ttSShort:
            {
            value = 0x8000;
            break;
            }

        default:
            {

            ThrowNotYetImplemented ();

            }

        }

    SetConstant (area,
                 plane,
                 planes,
                 value);

    }

/*****************************************************************************/

void dng_pixel_buffer::CopyArea (const dng_pixel_buffer &src,
                                 const dng_rect &area,
                                 uint32 srcPlane,
                                 uint32 dstPlane,
                                 uint32 planes)
    {

    uint32 rows = area.H ();
    uint32 cols = area.W ();

    const void *sPtr = src.ConstPixel (area.t,
                                       area.l,
                                       srcPlane);

    void *dPtr = DirtyPixel (area.t,
                             area.l,
                             dstPlane);

    int32 sRowStep   = src.fRowStep;
    int32 sColStep   = src.fColStep;
    int32 sPlaneStep = src.fPlaneStep;

    int32 dRowStep   = fRowStep;
    int32 dColStep   = fColStep;
    int32 dPlaneStep = fPlaneStep;

    OptimizeOrder (sPtr,
                   dPtr,
                   src.fPixelSize,
                   fPixelSize,
                   rows,
                   cols,
                   planes,
                   sRowStep,
                   sColStep,
                   sPlaneStep,
                   dRowStep,
                   dColStep,
                   dPlaneStep);

    if (fPixelType == src.fPixelType)
        {

        if (rows == 1 && cols == 1 && sPlaneStep == 1 && dPlaneStep == 1)
            {

            DoCopyBytes (sPtr,
                         dPtr,
                         planes * fPixelSize);

            }

        else switch (fPixelSize)
            {

            case 1:
                {

                DoCopyArea8 ((const uint8 *) sPtr,
                             (uint8 *) dPtr,
                             rows,
                             cols,
                             planes,
                             sRowStep,
                             sColStep,
                             sPlaneStep,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                break;

                }

            case 2:
                {

                DoCopyArea16 ((const uint16 *) sPtr,
                              (uint16 *) dPtr,
                              rows,
                              cols,
                              planes,
                              sRowStep,
                              sColStep,
                              sPlaneStep,
                              dRowStep,
                              dColStep,
                              dPlaneStep);

                break;

                }

            case 4:
                {

                DoCopyArea32 ((const uint32 *) sPtr,
                              (uint32 *) dPtr,
                              rows,
                              cols,
                              planes,
                              sRowStep,
                              sColStep,
                              sPlaneStep,
                              dRowStep,
                              dColStep,
                              dPlaneStep);

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else if (src.fPixelType == ttByte)
        {

        switch (fPixelType)
            {

            case ttShort:
                {

                DoCopyArea8_16 ((const uint8 *) sPtr,
                                (uint16 *) dPtr,
                                rows,
                                cols,
                                planes,
                                sRowStep,
                                sColStep,
                                sPlaneStep,
                                dRowStep,
                                dColStep,
                                dPlaneStep);

                break;

                }

            case ttSShort:
                {

                DoCopyArea8_S16 ((const uint8 *) sPtr,
                                 (int16 *) dPtr,
                                 rows,
                                 cols,
                                 planes,
                                 sRowStep,
                                 sColStep,
                                 sPlaneStep,
                                 dRowStep,
                                 dColStep,
                                 dPlaneStep);

                break;

                }

            case ttLong:
                {

                DoCopyArea8_32 ((const uint8 *) sPtr,
                                (uint32 *) dPtr,
                                rows,
                                cols,
                                planes,
                                sRowStep,
                                sColStep,
                                sPlaneStep,
                                dRowStep,
                                dColStep,
                                dPlaneStep);

                break;

                }

            case ttFloat:
                {

                DoCopyArea8_R32 ((const uint8 *) sPtr,
                                 (real32 *) dPtr,
                                 rows,
                                 cols,
                                 planes,
                                 sRowStep,
                                 sColStep,
                                 sPlaneStep,
                                 dRowStep,
                                 dColStep,
                                 dPlaneStep,
                                 src.PixelRange ());

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else if (src.fPixelType == ttShort)
        {

        switch (fPixelType)
            {

            case ttByte:
                {

                DoCopyArea8 (((const uint8 *) sPtr) + (qDNGBigEndian ? 1 : 0),
                             (uint8 *) dPtr,
                             rows,
                             cols,
                             planes,
                             sRowStep << 1,
                             sColStep << 1,
                             sPlaneStep << 1,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                break;

                }

            case ttSShort:
                {

                DoCopyArea16_S16 ((const uint16 *) sPtr,
                                  (int16 *) dPtr,
                                  rows,
                                  cols,
                                  planes,
                                  sRowStep,
                                  sColStep,
                                  sPlaneStep,
                                  dRowStep,
                                  dColStep,
                                  dPlaneStep);

                break;

                }

            case ttLong:
                {

                DoCopyArea16_32 ((const uint16 *) sPtr,
                                 (uint32 *) dPtr,
                                 rows,
                                 cols,
                                 planes,
                                 sRowStep,
                                 sColStep,
                                 sPlaneStep,
                                 dRowStep,
                                 dColStep,
                                 dPlaneStep);

                break;

                }

            case ttFloat:
                {

                DoCopyArea16_R32 ((const uint16 *) sPtr,
                                  (real32 *) dPtr,
                                  rows,
                                  cols,
                                  planes,
                                  sRowStep,
                                  sColStep,
                                  sPlaneStep,
                                  dRowStep,
                                  dColStep,
                                  dPlaneStep,
                                  src.PixelRange ());

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else if (src.fPixelType == ttSShort)
        {

        switch (fPixelType)
            {

            case ttByte:
                {

                DoCopyArea8 (((const uint8 *) sPtr) + (qDNGBigEndian ? 1 : 0),
                             (uint8 *) dPtr,
                             rows,
                             cols,
                             planes,
                             sRowStep << 1,
                             sColStep << 1,
                             sPlaneStep << 1,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                break;

                }

            case ttShort:
                {

                // Moving between signed 16 bit values and unsigned 16
                // bit values just requires toggling the sign bit.  So
                // we can use the "backwards" bottleneck.

                DoCopyArea16_S16 ((const uint16 *) sPtr,
                                  (int16 *) dPtr,
                                  rows,
                                  cols,
                                  planes,
                                  sRowStep,
                                  sColStep,
                                  sPlaneStep,
                                  dRowStep,
                                  dColStep,
                                  dPlaneStep);

                break;

                }

            case ttFloat:
                {

                DoCopyAreaS16_R32 ((const int16 *) sPtr,
                                   (real32 *) dPtr,
                                   rows,
                                   cols,
                                   planes,
                                   sRowStep,
                                   sColStep,
                                   sPlaneStep,
                                   dRowStep,
                                   dColStep,
                                   dPlaneStep,
                                   src.PixelRange ());

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else if (src.fPixelType == ttLong)
        {

        switch (fPixelType)
            {

            case ttByte:
                {

                DoCopyArea8 (((const uint8 *) sPtr) + (qDNGBigEndian ? 3 : 0),
                             (uint8 *) dPtr,
                             rows,
                             cols,
                             planes,
                             sRowStep << 2,
                             sColStep << 2,
                             sPlaneStep << 2,
                             dRowStep,
                             dColStep,
                             dPlaneStep);

                break;

                }

            case ttShort:
                {

                DoCopyArea16 (((const uint16 *) sPtr) + (qDNGBigEndian ? 1 : 0),
                              (uint16 *) dPtr,
                              rows,
                              cols,
                              planes,
                              sRowStep << 1,
                              sColStep << 1,
                              sPlaneStep << 1,
                              dRowStep,
                              dColStep,
                              dPlaneStep);

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else if (src.fPixelType == ttFloat)
        {

        switch (fPixelType)
            {

            case ttByte:
                {

                DoCopyAreaR32_8 ((const real32 *) sPtr,
                                 (uint8 *) dPtr,
                                 rows,
                                 cols,
                                 planes,
                                 sRowStep,
                                 sColStep,
                                 sPlaneStep,
                                 dRowStep,
                                 dColStep,
                                 dPlaneStep,
                                 PixelRange ());

                break;

                }

            case ttShort:
                {

                DoCopyAreaR32_16 ((const real32 *) sPtr,
                                  (uint16 *) dPtr,
                                  rows,
                                  cols,
                                  planes,
                                  sRowStep,
                                  sColStep,
                                  sPlaneStep,
                                  dRowStep,
                                  dColStep,
                                  dPlaneStep,
                                  PixelRange ());

                break;

                }

            case ttSShort:
                {

                DoCopyAreaR32_S16 ((const real32 *) sPtr,
                                   (int16 *) dPtr,
                                   rows,
                                   cols,
                                   planes,
                                   sRowStep,
                                   sColStep,
                                   sPlaneStep,
                                   dRowStep,
                                   dColStep,
                                   dPlaneStep,
                                   PixelRange ());

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                }

            }

        }

    else
        {

        ThrowNotYetImplemented ();

        }

    }

/*****************************************************************************/

dng_point dng_pixel_buffer::RepeatPhase (const dng_rect &srcArea,
                                         const dng_rect &dstArea)
    {

    int32 repeatV = srcArea.H ();
    int32 repeatH = srcArea.W ();

    int32 phaseV;
    int32 phaseH;

    if (repeatV == 0 ||
        repeatH == 0)
        {
        DNG_REPORT ("Bad srcArea in RepeatPhase");
        return dng_point ();
        }

    if (srcArea.t >= dstArea.t)
        {
        phaseV = (repeatV - ((srcArea.t - dstArea.t) % repeatV)) % repeatV;
        }
    else
        {
        phaseV = (dstArea.t - srcArea.t) % repeatV;
        }

    if (srcArea.l >= dstArea.l)
        {
        phaseH = (repeatH - ((srcArea.l - dstArea.l) % repeatH)) % repeatH;
        }
    else
        {
        phaseH = (dstArea.l - srcArea.l) % repeatH;
        }

    return dng_point (phaseV, phaseH);

    }

/*****************************************************************************/

void dng_pixel_buffer::RepeatArea (const dng_rect &srcArea,
                                   const dng_rect &dstArea)
    {

    dng_point repeat = srcArea.Size ();

    dng_point phase = RepeatPhase (srcArea,
                                   dstArea);

    const void *sPtr = ConstPixel (srcArea.t,
                                   srcArea.l,
                                   fPlane);

    void *dPtr = DirtyPixel (dstArea.t,
                             dstArea.l,
                             fPlane);

    uint32 rows = dstArea.H ();
    uint32 cols = dstArea.W ();

    switch (fPixelSize)
        {

        case 1:
            {

            DoRepeatArea8 ((const uint8 *) sPtr,
                           (uint8 *) dPtr,
                           rows,
                           cols,
                           fPlanes,
                           fRowStep,
                           fColStep,
                           fPlaneStep,
                           repeat.v,
                           repeat.h,
                           phase.v,
                           phase.h);

            break;

            }

        case 2:
            {

            DoRepeatArea16 ((const uint16 *) sPtr,
                            (uint16 *) dPtr,
                            rows,
                            cols,
                            fPlanes,
                            fRowStep,
                            fColStep,
                            fPlaneStep,
                            repeat.v,
                            repeat.h,
                            phase.v,
                            phase.h);

            break;

            }

        case 4:
            {

            DoRepeatArea32 ((const uint32 *) sPtr,
                            (uint32 *) dPtr,
                            rows,
                            cols,
                            fPlanes,
                            fRowStep,
                            fColStep,
                            fPlaneStep,
                            repeat.v,
                            repeat.h,
                            phase.v,
                            phase.h);

            break;

            }

        default:
            {

            ThrowNotYetImplemented ();

            }

        }

    }

/*****************************************************************************/

void dng_pixel_buffer::RepeatSubArea (const dng_rect subArea,
                                      uint32 repeatV,
                                      uint32 repeatH)
    {

    if (fArea.t < subArea.t)
        {

        RepeatArea (dng_rect (subArea.t          , fArea.l,
                              subArea.t + repeatV, fArea.r),
                    dng_rect (fArea.t            , fArea.l,
                              subArea.t          , fArea.r));

        }

    if (fArea.b > subArea.b)
        {

        RepeatArea (dng_rect (subArea.b - repeatV, fArea.l,
                              subArea.b          , fArea.r),
                    dng_rect (subArea.b          , fArea.l,
                              fArea.b            , fArea.r));

        }

    if (fArea.l < subArea.l)
        {

        RepeatArea (dng_rect (fArea.t, subArea.l          ,
                              fArea.b, subArea.l + repeatH),
                    dng_rect (fArea.t, fArea.l            ,
                              fArea.b, subArea.l          ));

        }

    if (fArea.r > subArea.r)
        {

        RepeatArea (dng_rect (fArea.t, subArea.r - repeatH,
                              fArea.b, subArea.r          ),
                    dng_rect (fArea.t, subArea.r          ,
                              fArea.b, fArea.r            ));

        }

    }

/*****************************************************************************/

void dng_pixel_buffer::ShiftRight (uint32 shift)
    {

    if (fPixelType != ttShort)
        {

        ThrowNotYetImplemented ();

        }

    uint32 rows = fArea.H ();
    uint32 cols = fArea.W ();

    uint32 planes = fPlanes;

    void *dPtr = DirtyPixel (fArea.t,
                             fArea.l,
                             fPlane);

    const void *sPtr = dPtr;

    int32 sRowStep   = fRowStep;
    int32 sColStep   = fColStep;
    int32 sPlaneStep = fPlaneStep;

    int32 dRowStep   = fRowStep;
    int32 dColStep   = fColStep;
    int32 dPlaneStep = fPlaneStep;

    OptimizeOrder (sPtr,
                   dPtr,
                   fPixelSize,
                   fPixelSize,
                   rows,
                   cols,
                   planes,
                   sRowStep,
                   sColStep,
                   sPlaneStep,
                   dRowStep,
                   dColStep,
                   dPlaneStep);

    DoShiftRight16 ((uint16 *) dPtr,
                    rows,
                    cols,
                    planes,
                    dRowStep,
                    dColStep,
                    dPlaneStep,
                    shift);

    }

/*****************************************************************************/

void dng_pixel_buffer::FlipH ()
    {

    fData = InternalPixel (fArea.t, fArea.r - 1);

    fColStep = -fColStep;

    }

/*****************************************************************************/

void dng_pixel_buffer::FlipV ()
    {

    fData = InternalPixel (fArea.b - 1, fArea.l);

    fRowStep = -fRowStep;

    }

/*****************************************************************************/

void dng_pixel_buffer::FlipZ ()
    {

    fData = InternalPixel (fArea.t, fArea.l, fPlanes - 1);

    fPlaneStep = -fPlaneStep;

    }

/*****************************************************************************/

bool dng_pixel_buffer::EqualArea (const dng_pixel_buffer &src,
                                  const dng_rect &area,
                                  uint32 plane,
                                  uint32 planes) const
    {

    uint32 rows = area.H ();
    uint32 cols = area.W ();

    const void *sPtr = src.ConstPixel (area.t,
                                       area.l,
                                       plane);

    const void *dPtr = ConstPixel (area.t,
                                   area.l,
                                   plane);

    int32 sRowStep   = src.fRowStep;
    int32 sColStep   = src.fColStep;
    int32 sPlaneStep = src.fPlaneStep;

    int32 dRowStep   = fRowStep;
    int32 dColStep   = fColStep;
    int32 dPlaneStep = fPlaneStep;

    if (fPixelType == src.fPixelType)
        {

        if (rows == 1 && cols == 1 && sPlaneStep == 1 && dPlaneStep == 1)
            {

            return DoEqualBytes (sPtr,
                                 dPtr,
                                 planes * fPixelSize);

            }

        else switch (fPixelSize)
            {

            case 1:
                {

                return DoEqualArea8 ((const uint8 *) sPtr,
                                     (const uint8 *) dPtr,
                                     rows,
                                     cols,
                                     planes,
                                     sRowStep,
                                     sColStep,
                                     sPlaneStep,
                                     dRowStep,
                                     dColStep,
                                     dPlaneStep);

                break;

                }

            case 2:
                {

                return DoEqualArea16 ((const uint16 *) sPtr,
                                      (const uint16 *) dPtr,
                                      rows,
                                      cols,
                                      planes,
                                      sRowStep,
                                      sColStep,
                                      sPlaneStep,
                                      dRowStep,
                                      dColStep,
                                      dPlaneStep);

                break;

                }

            case 4:
                {

                return DoEqualArea32 ((const uint32 *) sPtr,
                                      (const uint32 *) dPtr,
                                      rows,
                                      cols,
                                      planes,
                                      sRowStep,
                                      sColStep,
                                      sPlaneStep,
                                      dRowStep,
                                      dColStep,
                                      dPlaneStep);

                break;

                }

            default:
                {

                ThrowNotYetImplemented ();

                return false;

                }

            }

        }

    else
        return false;

    }

/*****************************************************************************/

namespace
    {

    template <typename T>
    real64 MaxDiff (const T *src1,
                    int32 s1RowStep,
                    int32 s1PlaneStep,
                    const T *src2,
                    int32 s2RowStep,
                    int32 s2PlaneStep,
                    uint32 rows,
                    uint32 cols,
                    uint32 planes)
        {

        real64 result = 0.0;

        for (uint32 plane = 0; plane < planes; plane++)
            {

            const T *src1Save = src1;
            const T *src2Save = src2;

            for (uint32 row = 0; row < rows; row++)
                {

                for (uint32 col = 0; col < cols; col++)
                    {
                    real64 diff = fabs ((real64)src1 [col] - src2 [col]);

                    if (diff > result)
                        result = diff;

                    }

                src1 += s1RowStep;
                src2 += s2RowStep;

                }

            src1 = src1Save + s1PlaneStep;
            src2 = src2Save + s2PlaneStep;

            }

        return result;

        }

    template <typename T>
    real64 MaxDiff (const T *src1,
                    int32 s1ColStep,
                    int32 s1RowStep,
                    int32 s1PlaneStep,
                    const T *src2,
                    int32 s2ColStep,
                    int32 s2RowStep,
                    int32 s2PlaneStep,
                    uint32 rows,
                    uint32 cols,
                    uint32 planes)
        {

        if (s1ColStep == s2ColStep &&
            s1ColStep == 1)
            return MaxDiff (src1,
                            s1RowStep,
                            s1PlaneStep,
                            src2,
                            s2RowStep,
                            s2PlaneStep,
                            rows,
                            cols,
                            planes);

        real64 result = 0.0;

        for (uint32 plane = 0; plane < planes; plane++)
            {

            const T *src1Save = src1;
            const T *src2Save = src2;

            for (uint32 row = 0; row < rows; row++)
                {

                for (uint32 col = 0; col < cols; col++)
                    {
                    real64 diff = fabs ((real64)src1 [col * s1ColStep] - src2 [col * s2ColStep]);

                    if (diff > result)
                        result = diff;

                    }

                src1 += s1RowStep;
                src2 += s2RowStep;

                }

            src1 = src1Save + s1PlaneStep;
            src2 = src2Save + s2PlaneStep;

            }


        return result;

        }
    }

real64 dng_pixel_buffer::MaximumDifference (const dng_pixel_buffer &rhs,
                                            const dng_rect &area,
                                            uint32 plane,
                                            uint32 planes) const
    {

    uint32 rows = area.H ();
    uint32 cols = area.W ();

    const void *s1Ptr = rhs.ConstPixel (area.t,
                                        area.l,
                                        plane);

    const void *s2Ptr = ConstPixel (area.t,
                                    area.l,
                                    plane);

    int32 s1RowStep   = rhs.fRowStep;
    int32 s1ColStep   = rhs.fColStep;
    int32 s1PlaneStep = rhs.fPlaneStep;

    int32 s2RowStep   = fRowStep;
    int32 s2ColStep   = fColStep;
    int32 s2PlaneStep = fPlaneStep;

    if (fPixelType == rhs.fPixelType)
        {

        switch (fPixelType)
            {

            case ttByte:
                return MaxDiff ((const uint8 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const uint8 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttShort:
                return MaxDiff ((const uint16 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const uint16 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttLong:
                return MaxDiff ((const uint32 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const uint32 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttSByte:
                return MaxDiff ((const int8 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const int8 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttSShort:
                return MaxDiff ((const int16 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const int16 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttSLong:
                return MaxDiff ((const int32 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const int32 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttFloat:
                return MaxDiff ((const real32 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const real32 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;

            case ttDouble:
                return MaxDiff ((const real64 *)s1Ptr,
                                s1ColStep,
                                s1RowStep,
                                s1PlaneStep,
                                (const real64 *)s2Ptr,
                                s2ColStep,
                                s2RowStep,
                                s2PlaneStep,
                                rows,
                                cols,
                                planes);

                break;


            default:
                {

                ThrowNotYetImplemented ();

                return 0.0;

                }

            }

        }

    else
        ThrowProgramError ("attempt to difference pixel buffers of different formats.");

    return 0.0;

    }

/*****************************************************************************/
