/*****************************************************************************/
// 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_image.h"

#include "dng_assertions.h"
#include "dng_exceptions.h"
#include "dng_orientation.h"
#include "dng_pixel_buffer.h"
#include "dng_tag_types.h"
#include "dng_tile_iterator.h"
#include "dng_utils.h"

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

dng_tile_buffer::dng_tile_buffer (const dng_image &image,
                                  const dng_rect &tile,
                                  bool dirty)

    :   fImage   (image)
    ,   fRefData (NULL)

    {

    fImage.AcquireTileBuffer (*this,
                              tile,
                              dirty);

    }

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

dng_tile_buffer::~dng_tile_buffer ()
    {

    fImage.ReleaseTileBuffer (*this);

    }

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

dng_const_tile_buffer::dng_const_tile_buffer (const dng_image &image,
                                              const dng_rect &tile)

    :   dng_tile_buffer (image, tile, false)

    {

    }

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

dng_const_tile_buffer::~dng_const_tile_buffer ()
    {

    }

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

dng_dirty_tile_buffer::dng_dirty_tile_buffer (dng_image &image,
                                              const dng_rect &tile)

    :   dng_tile_buffer (image, tile, true)

    {

    }

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

dng_dirty_tile_buffer::~dng_dirty_tile_buffer ()
    {

    }

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

dng_image::dng_image (const dng_rect &bounds,
                      uint32 planes,
                      uint32 pixelType)

    :   fBounds    (bounds)
    ,   fPlanes    (planes)
    ,   fPixelType (pixelType)

    {

    if (bounds.IsEmpty () || planes == 0 || PixelSize () == 0)
        {

        #if qDNGValidate

        ReportError ("Fuzz: Attempt to create zero size image");

        #endif

        ThrowBadFormat ();

        }

    // Allow up to 2 * kMaxImageSide to deal with intermediate image objects
    // (e.g., rotated and padded).

    static const uint32 kLimit = 2 * kMaxImageSide;

    if (bounds.W () > kLimit ||
        bounds.H () > kLimit)
        {

        ThrowBadFormat ("dng_image bounds too large");

        }

    }

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

dng_image::~dng_image ()
    {

    }

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

dng_image * dng_image::Clone () const
    {

    ThrowProgramError ("Clone is not supported by this dng_image subclass");

    return NULL;

    }

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

void dng_image::SetPixelType (uint32 pixelType)
    {

    if (TagTypeSize (pixelType) != PixelSize ())
        {

        ThrowProgramError ("Cannot change pixel size for existing image");

        }

    fPixelType = pixelType;

    }

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

uint32 dng_image::PixelSize () const
    {

    return TagTypeSize (PixelType ());

    }

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

uint32 dng_image::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;

    }

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

dng_rect dng_image::RepeatingTile () const
    {

    return fBounds;

    }

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

void dng_image::AcquireTileBuffer (dng_tile_buffer & /* buffer */,
                                   const dng_rect & /* area */,
                                   bool /* dirty */) const
    {

    ThrowProgramError ();

    }

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

void dng_image::ReleaseTileBuffer (dng_tile_buffer & /* buffer */) const
    {

    }

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

void dng_image::DoGet (dng_pixel_buffer &buffer) const
    {

    dng_rect tile;

    dng_tile_iterator iter (*this, buffer.fArea);

    while (iter.GetOneTile (tile))
        {

        dng_const_tile_buffer tileBuffer (*this, tile);

        buffer.CopyArea (tileBuffer,
                         tile,
                         buffer.fPlane,
                         buffer.fPlanes);

        }

    }

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

void dng_image::DoPut (const dng_pixel_buffer &buffer)
    {

    dng_rect tile;

    dng_tile_iterator iter (*this, buffer.fArea);

    while (iter.GetOneTile (tile))
        {

        dng_dirty_tile_buffer tileBuffer (*this, tile);

        tileBuffer.CopyArea (buffer,
                             tile,
                             buffer.fPlane,
                             buffer.fPlanes);

        }

    }

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

void dng_image::GetRepeat (dng_pixel_buffer &buffer,
                           const dng_rect &srcArea,
                           const dng_rect &dstArea) const
    {

    // If we already have the entire srcArea in the
    // buffer, we can just repeat that.

    if ((srcArea & buffer.fArea) == srcArea)
        {

        buffer.RepeatArea (srcArea,
                           dstArea);

        }

    // Else we first need to get the srcArea into the buffer area.

    else
        {

        // Find repeating pattern size.

        dng_point repeat = srcArea.Size ();

        // Find pattern phase at top-left corner of destination area.

        dng_point phase = dng_pixel_buffer::RepeatPhase (srcArea,
                                                         dstArea);

        // Find new source area at top-left of dstArea.

        dng_rect newArea = srcArea + (dstArea.TL () -
                                      srcArea.TL ());

        // Find quadrant split coordinates.

        int32 splitV = newArea.t + repeat.v - phase.v;
        int32 splitH = newArea.l + repeat.h - phase.h;

        // Top-left quadrant.

        dng_rect dst1 (dng_rect (newArea.t,
                                 newArea.l,
                                 splitV,
                                 splitH) & dstArea);

        if (dst1.NotEmpty ())
            {

            dng_pixel_buffer temp (buffer);

            temp.fArea = dst1 + (srcArea.TL () -
                                 dstArea.TL () +
                                 dng_point (phase.v, phase.h));

            temp.fData = buffer.DirtyPixel (dst1.t,
                                            dst1.l,
                                            buffer.fPlane);

            DoGet (temp);

            }

        // Top-right quadrant.

        dng_rect dst2 (dng_rect (newArea.t,
                                 splitH,
                                 splitV,
                                 newArea.r) & dstArea);

        if (dst2.NotEmpty ())
            {

            dng_pixel_buffer temp (buffer);

            temp.fArea = dst2 + (srcArea.TL () -
                                 dstArea.TL () +
                                 dng_point (phase.v, -phase.h));

            temp.fData = buffer.DirtyPixel (dst2.t,
                                            dst2.l,
                                            buffer.fPlane);

            DoGet (temp);

            }

        // Bottom-left quadrant.

        dng_rect dst3 (dng_rect (splitV,
                                 newArea.l,
                                 newArea.b,
                                 splitH) & dstArea);

        if (dst3.NotEmpty ())
            {

            dng_pixel_buffer temp (buffer);

            temp.fArea = dst3 + (srcArea.TL () -
                                 dstArea.TL () +
                                 dng_point (-phase.v, phase.h));

            temp.fData = buffer.DirtyPixel (dst3.t,
                                            dst3.l,
                                            buffer.fPlane);

            DoGet (temp);

            }

        // Bottom-right quadrant.

        dng_rect dst4 (dng_rect (splitV,
                                 splitH,
                                 newArea.b,
                                 newArea.r) & dstArea);

        if (dst4.NotEmpty ())
            {

            dng_pixel_buffer temp (buffer);

            temp.fArea = dst4 + (srcArea.TL () -
                                 dstArea.TL () +
                                 dng_point (-phase.v, -phase.h));

            temp.fData = buffer.DirtyPixel (dst4.t,
                                            dst4.l,
                                            buffer.fPlane);

            DoGet (temp);

            }

        // Replicate this new source area.

        buffer.RepeatArea (newArea,
                           dstArea);

        }

    }

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

void dng_image::GetEdge (dng_pixel_buffer &buffer,
                         edge_option edgeOption,
                         const dng_rect &srcArea,
                         const dng_rect &dstArea) const
    {

    switch (edgeOption)
        {

        case edge_zero:
            {

            buffer.SetZero (dstArea,
                            buffer.fPlane,
                            buffer.fPlanes);

            break;

            }

        case edge_repeat:
            {

            GetRepeat (buffer,
                       srcArea,
                       dstArea);

            break;

            }

        case edge_repeat_zero_last:
            {

            if (buffer.fPlanes > 1)
                {

                dng_pixel_buffer buffer1 (buffer);

                buffer1.fPlanes--;

                GetEdge (buffer1,
                         edge_repeat,
                         srcArea,
                         dstArea);

                }

            dng_pixel_buffer buffer2 (buffer);

            buffer2.fPlane  = buffer.fPlanes - 1;
            buffer2.fPlanes = 1;

            buffer2.fData = buffer.DirtyPixel (buffer2.fArea.t,
                                               buffer2.fArea.l,
                                               buffer2.fPlane);

            GetEdge (buffer2,
                     edge_zero,
                     srcArea,
                     dstArea);

            break;

            }

        default:
            {

            ThrowProgramError ();

            }

        }

    }

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

static void WrapLeft (dng_pixel_buffer &buffer,
                      const dng_image &image,
                      const dng_rect &dstArea,
                      const int32 srcTop,
                      const int32 srcBottom)
    {

    const dng_rect imageBounds = image.Bounds ();

    int32 remainingPixels = (int32) dstArea.W ();

    int32 maxPixelsPerIter = (int32) imageBounds.W ();

    int32 dstLeft = Min_int32 (imageBounds.l, dstArea.r);

    DNG_REQUIRE (imageBounds.W () > 0, "WrapLeft: imageBounds.W");

    int32 dstLeftPhase = (imageBounds.l - dstLeft) % imageBounds.W ();

    while (remainingPixels > 0)
        {

        int32 srcRight = imageBounds.r - dstLeftPhase;

        int32 pixelCount = Min_int32 (remainingPixels,
                                      maxPixelsPerIter - dstLeftPhase);

        dstLeft -= pixelCount;

        dng_rect srcArea (srcTop,
                          srcRight - pixelCount,
                          srcBottom,
                          srcRight);

        auto tmpBuffer = buffer;

        void *ptr = tmpBuffer.DirtyPixel (dstArea.t,
                                          dstLeft);

        tmpBuffer.fArea = srcArea;

        tmpBuffer.fData = ptr;

        image.Get (tmpBuffer,
                   dng_image::edge_none);

        remainingPixels -= pixelCount;

        dstLeftPhase = 0; // Only valid/needed if remainingPixels > 0.

        }

    }

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

static void WrapRight (dng_pixel_buffer &buffer,
                       const dng_image &image,
                       const dng_rect &dstArea,
                       const int32 srcTop,
                       const int32 srcBottom)
    {

    const dng_rect imageBounds = image.Bounds ();

    int32 remainingPixels = (int32) dstArea.W ();

    int32 maxPixelsPerIter = (int32) imageBounds.W ();

    int32 dstLeft = Max_int32 (imageBounds.r, dstArea.l);

    DNG_REQUIRE (imageBounds.W () > 0, "WrapRight: imageBounds.W");

    int32 dstLeftPhase = (dstLeft - imageBounds.r) % imageBounds.W ();

    while (remainingPixels > 0)
        {

        int32 srcLeft = imageBounds.l + dstLeftPhase;

        int32 pixelCount = Min_int32 (remainingPixels,
                                      maxPixelsPerIter - dstLeftPhase);

        dng_rect srcArea (srcTop,
                          srcLeft,
                          srcBottom,
                          srcLeft + pixelCount);

        auto tmpBuffer = buffer;

        void *ptr = tmpBuffer.DirtyPixel (dstArea.t,
                                          dstLeft);

        tmpBuffer.fArea = srcArea;

        tmpBuffer.fData = ptr;

        image.Get (tmpBuffer,
                   dng_image::edge_none);

        dstLeft += pixelCount;

        remainingPixels -= pixelCount;

        dstLeftPhase = 0; // Only valid/needed if remainingPixels > 0.

        }

    }

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

static void WrapCornerHorizontal (dng_pixel_buffer &buffer,
                                  const dng_image &image,
                                  const dng_rect &dstArea,
                                  uint32 repeatV,
                                  bool isTop,
                                  bool isLeft)
    {

    if (dstArea.IsEmpty ())
        {
        return;
        }

    const dng_rect imageBounds = image.Bounds ();

    uint32 repeatSize = repeatV;

    repeatSize = Max_uint32 (repeatSize, 1);

    repeatSize = Min_uint32 (repeatSize, imageBounds.H ());

    repeatSize = Min_uint32 (repeatSize, dstArea.H ());

    auto tmpArea = dstArea;

    const int32 iRepeatSize = (int32) repeatSize;

    if (isTop)
        {
        tmpArea.t = tmpArea.b - iRepeatSize;
        }

    else
        {
        tmpArea.b = tmpArea.t + iRepeatSize;
        }

    if (isLeft)
        {

        WrapLeft (buffer,
                  image,
                  tmpArea,
                  isTop ? (imageBounds.t) : (imageBounds.b - iRepeatSize),
                  isTop ? (imageBounds.t + iRepeatSize) : (imageBounds.b));

        }

    else
        {

        WrapRight (buffer,
                   image,
                   tmpArea,
                   isTop ? (imageBounds.t) : (imageBounds.b - iRepeatSize),
                   isTop ? (imageBounds.t + iRepeatSize) : (imageBounds.b));

        }

    buffer.RepeatArea (tmpArea,
                       dstArea);

    }

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

void dng_image::Get (dng_pixel_buffer &buffer,
                     edge_option edgeOption,
                     uint32 repeatV,
                     uint32 repeatH) const
    {

    // Find the overlap with the image bounds.

    dng_rect overlap = buffer.fArea & fBounds;

    // Move the overlapping pixels.

    if (overlap.NotEmpty ())
        {

        dng_pixel_buffer temp (buffer);

        temp.fArea = overlap;

        temp.fData = buffer.DirtyPixel (overlap.t,
                                        overlap.l,
                                        buffer.fPlane);

        DoGet (temp);

        }

    // Throw if unsupported edge wrap options are found.

    if (edgeOption == edge_wrap_vertical ||
        edgeOption == edge_wrap_all)
        {

        ThrowNotYetImplemented ("Unsupported edge option");

        }

    // See if we need to pad the edge values.

    if ((edgeOption != edge_none) && (overlap != buffer.fArea))
        {

        dng_rect areaT (buffer.fArea);
        dng_rect areaL (buffer.fArea);
        dng_rect areaB (buffer.fArea);
        dng_rect areaR (buffer.fArea);

        areaT.b = Min_int32 (areaT.b, fBounds.t);
        areaL.r = Min_int32 (areaL.r, fBounds.l);
        areaB.t = Max_int32 (areaB.t, fBounds.b);
        areaR.l = Max_int32 (areaR.l, fBounds.r);

        dng_rect areaH (buffer.fArea);
        dng_rect areaV (buffer.fArea);

        areaH.l = Max_int32 (areaH.l, fBounds.l);
        areaH.r = Min_int32 (areaH.r, fBounds.r);

        areaV.t = Max_int32 (areaV.t, fBounds.t);
        areaV.b = Min_int32 (areaV.b, fBounds.b);

        // Top left.

        dng_rect areaTL = areaT & areaL;

        if (areaTL.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapCornerHorizontal (buffer,
                                      *this,
                                      areaTL,
                                      repeatV,
                                      true,  // is top?
                                      true); // is left?

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (fBounds.t,
                                   fBounds.l,
                                   fBounds.t + (int32) repeatV,
                                   fBounds.l + (int32) repeatH),
                         areaTL);

                }

            }

        // Top middle.

        dng_rect areaTM = areaT & areaH;

        if (areaTM.NotEmpty ())
            {

            edge_option tmpEdgeOption = edgeOption;

            if (edgeOption == edge_wrap_horizontal)
                {
                tmpEdgeOption = edge_repeat;
                }

            GetEdge (buffer,
                     tmpEdgeOption,
                     dng_rect (fBounds.t,
                               areaTM.l,
                               fBounds.t + (int32) repeatV,
                               areaTM.r),
                     areaTM);

            }

        // Top right.

        dng_rect areaTR = areaT & areaR;

        if (areaTR.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapCornerHorizontal (buffer,
                                      *this,
                                      areaTR,
                                      repeatV,
                                      true,   // is top?
                                      false); // is left?

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (fBounds.t,
                                   fBounds.r - (int32) repeatH,
                                   fBounds.t + (int32) repeatV,
                                   fBounds.r),
                         areaTR);

                }

            }

        // Left middle.

        dng_rect areaLM = areaL & areaV;

        if (areaLM.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapLeft (buffer,
                          *this,
                          areaLM,
                          areaLM.t,
                          areaLM.b);

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (areaLM.t,
                                   fBounds.l,
                                   areaLM.b,
                                   fBounds.l + (int32) repeatH),
                         areaLM);

                }

            }

        // Right middle.

        dng_rect areaRM = areaR & areaV;

        if (areaRM.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapRight (buffer,
                           *this,
                           areaRM,
                           areaRM.t,
                           areaRM.b);

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (areaRM.t,
                                   fBounds.r - (int32) repeatH,
                                   areaRM.b,
                                   fBounds.r),
                         areaRM);

                }

            }

        // Bottom left.

        dng_rect areaBL = areaB & areaL;

        if (areaBL.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapCornerHorizontal (buffer,
                                      *this,
                                      areaBL,
                                      repeatV,
                                      false, // is top?
                                      true); // is left?

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (fBounds.b - (int32) repeatV,
                                   fBounds.l,
                                   fBounds.b,
                                   fBounds.l + (int32) repeatH),
                         areaBL);

                }

            }

        // Bottom middle.

        dng_rect areaBM = areaB & areaH;

        if (areaBM.NotEmpty ())
            {

            edge_option tmpEdgeOption = edgeOption;

            if (edgeOption == edge_wrap_horizontal)
                {
                tmpEdgeOption = edge_repeat;
                }

            GetEdge (buffer,
                     tmpEdgeOption,
                     dng_rect (fBounds.b - (int32) repeatV,
                               areaBM.l,
                               fBounds.b,
                               areaBM.r),
                     areaBM);

            }

        // Bottom right.

        dng_rect areaBR = areaB & areaR;

        if (areaBR.NotEmpty ())
            {

            if (edgeOption == edge_wrap_horizontal)
                {

                WrapCornerHorizontal (buffer,
                                      *this,
                                      areaBR,
                                      repeatV,
                                      false,  // is top?
                                      false); // is left?

                }

            else
                {

                GetEdge (buffer,
                         edgeOption,
                         dng_rect (fBounds.b - (int32) repeatV,
                                   fBounds.r - (int32) repeatH,
                                   fBounds.b,
                                   fBounds.r),
                         areaBR);

                }

            }

        }

    }

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

void dng_image::Put (const dng_pixel_buffer &buffer)
    {

    // Move the overlapping pixels.

    dng_rect overlap = buffer.fArea & fBounds;

    if (overlap.NotEmpty ())
        {

        dng_pixel_buffer temp (buffer);

        temp.fArea = overlap;

        temp.fData = (void *) buffer.ConstPixel (overlap.t,
                                                 overlap.l,
                                                 buffer.fPlane);

        // Move the overlapping planes.

        if (temp.fPlane < Planes ())
            {

            temp.fPlanes = Min_uint32 (temp.fPlanes,
                                       Planes () - temp.fPlane);

            DoPut (temp);

            }

        }

    }

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

void dng_image::Trim (const dng_rect &r)
    {

    if (r != Bounds ())
        {

        ThrowProgramError ("Trim is not support by this dng_image subclass");

        }

    }

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

void dng_image::Rotate (const dng_orientation &orientation)
    {

    if (orientation != dng_orientation::Normal ())
        {

        ThrowProgramError ("Rotate is not support by this dng_image subclass");

        }

    }

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

void dng_image::Offset (const dng_point &offset)
    {

    if (offset != dng_point (0, 0))
        {

        ThrowProgramError ("Offset is not support by this dng_image subclass");

        }

    }

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

void dng_image::DoCopyArea (const dng_image &src,
                            const dng_rect &area,
                            uint32 srcPlane,
                            uint32 dstPlane,
                            uint32 planes)
    {

    if (&src == this)
        return;

    dng_tile_iterator destIter(*this, area);
    dng_rect destTileArea;

    while (destIter.GetOneTile(destTileArea))
        {
        dng_tile_iterator srcIter(src, destTileArea);
        dng_rect srcTileArea;

        while (srcIter.GetOneTile(srcTileArea))
            {

            dng_dirty_tile_buffer destTile(*this, srcTileArea);
            dng_const_tile_buffer srcTile(src, srcTileArea);

            destTile.CopyArea (srcTile, srcTileArea, srcPlane, dstPlane, planes);

            }

        }

    }

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

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

    if (&src == this)
        return true;

    dng_tile_iterator destIter (*this, area);

    dng_rect destTileArea;

    while (destIter.GetOneTile (destTileArea))
        {

        dng_tile_iterator srcIter (src, destTileArea);

        dng_rect srcTileArea;

        while (srcIter.GetOneTile (srcTileArea))
            {

            dng_const_tile_buffer destTile (*this, srcTileArea);
            dng_const_tile_buffer srcTile  (src  , srcTileArea);

            if (!destTile.EqualArea (srcTile, srcTileArea, plane, planes))
                {
                return false;
                }

            }

        }

    return true;

    }

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

void dng_image::SetConstant (uint32 value,
                             const dng_rect &area)
    {

    dng_tile_iterator iter (*this, area);

    dng_rect tileArea;

    while (iter.GetOneTile (tileArea))
        {

        dng_dirty_tile_buffer buffer (*this, tileArea);

        buffer.SetConstant (tileArea,
                            0,
                            fPlanes,
                            value);

        }

    }

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