/****************************************************************************
 * apps/graphics/nxwidgets/src/cscaledbitmap.hxx
 *
 *   Copyright (C) 2013-2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX, NxWidgets, nor the names of its contributors
 *    me be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>
#include <cstring>
#include <debug.h>

#include <nuttx/nx/nxglib.h>

#include "graphics/nxwidgets/cscaledbitmap.hxx"

/****************************************************************************
 * Pre-Processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Method Implementations
 ****************************************************************************/

using namespace NXWidgets;

/**
 * Constructor.
 *
 * @param bitmap The bitmap structure being scaled.
 * @newSize The new, scaled size of the image
 */

CScaledBitmap::CScaledBitmap(IBitmap *bitmap, struct nxgl_size_s &newSize)
: m_bitmap(bitmap), m_size(newSize)
{
  // xScale will be used to convert a request X position to an X position
  // in the contained bitmap:
  //
  // xImage = xRequested * oldWidth / newWidth
  //        = xRequested * xScale

  m_xScale = itob16((uint32_t)m_bitmap->getWidth()) / newSize.w;

  // Similarly, yScale will be used to convert a request Y position to a Y
  // positionin the contained bitmap:
  //
  // yImage = yRequested * oldHeight / newHeight
  //        = yRequested * yScale

  m_yScale = itob16((uint32_t)m_bitmap->getHeight()) / newSize.h;

  // Allocate and initialize the row cache

  size_t stride = bitmap->getStride();
  m_rowCache[0] = new uint8_t[stride];
  m_rowCache[1] = new uint8_t[stride];

  // Read the first two rows into the cache

  m_row = m_bitmap->getWidth(); // Set to an impossible value
  cacheRows(0);
}

/**
 * Destructor.
 */

CScaledBitmap::~CScaledBitmap(void)
{
  // Delete the allocated row cache memory

  if (m_rowCache[0])
    {
      delete m_rowCache[0];
    }

  if (m_rowCache[1])
    {
      delete m_rowCache[1];
   }

  // We are also responsible for deleting the contained IBitmap

  if (m_bitmap)
    {
      delete m_bitmap;
    }
}

/**
 * Get the bitmap's color format.
 *
 * @return The bitmap's width.
 */

const uint8_t CScaledBitmap::getColorFormat(void) const
{
  return m_bitmap->getColorFormat();
}

/**
 * Get the bitmap's color format.
 *
 * @return The bitmap's color format.
 */

const uint8_t CScaledBitmap::getBitsPerPixel(void) const
{
  return m_bitmap->getBitsPerPixel();
}

/**
 * Get the bitmap's width (in pixels/columns).
 *
 * @return The bitmap's pixel depth.
 */

const nxgl_coord_t CScaledBitmap::getWidth(void) const
{
  return m_size.w;
}

/**
 * Get the bitmap's height (in rows).
 *
 * @return The bitmap's height (in rows).
 */

const nxgl_coord_t CScaledBitmap::getHeight(void) const
{
  return m_size.h;
}

/**
 * Get the bitmap's width (in bytes).
 *
 * @return The bitmap's width (in bytes).
 */

const size_t CScaledBitmap::getStride(void) const
{
  return (m_bitmap->getBitsPerPixel() * m_size.w + 7) / 8;
}

/**
 * Get one row from the bit map image.
 *
 *   REVISIT:  This algorithm is really intended to expand images.  Hence,
 *   for example, interpolation is between row and row+1 and column and
 *   column+1 in the original, unscaled image.  You would the interpolation
 *   differently if you really wanted to sub-sample well.
 *
 * @param x The offset into the row to get
 * @param y The row number to get
 * @param width The number of pixels to get from the row
 * @param data The memory location provided by the caller
 *   in which to return the data.  This should be at least
 *   (getWidth()*getBitsPerPixl() + 7)/8 bytes in length
 *   and properly aligned for the pixel color format.
 * @param True if the run was returned successfully.
 */

bool CScaledBitmap::getRun(nxgl_coord_t x, nxgl_coord_t y,
                           nxgl_coord_t width, FAR void *data)
{
#if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332 || CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24
  FAR uint8_t  *dest = (FAR uint8_t *)data;
#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565
  FAR uint16_t *dest = (FAR uint16_t *)data;
#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32
  FAR uint32_t *dest = (FAR uint32_t *)data;
#else
#  error Unsupported, invalid, or undefined color format
#endif

  // Check ranges.  Casts to unsigned int are ugly but permit one-sided comparisons

  if (((unsigned int)x           >=  (unsigned int)m_size.w) &&
      ((unsigned int)(x + width) > (unsigned int)m_size.w) &&
      ((unsigned int)y           <= (unsigned int)m_size.h))
    {
      return false;
    }

  // Get the row number in the unscaled image corresponding to the
  // requested y position.  This must be either the exact row or the
  // closest row just before the requested position

  b16_t row16      = y * m_yScale;
  nxgl_coord_t row = b16toi(row16);

  // Get that row and the one after it into the row cache. We know that
  // the pixel value that we want is one between the two rows.  This
  // may seem wasteful to read two entire rows.  However, in normal usage
  // we will be traversal each image from top-left to bottom-right in
  // order.  In that case, the caching is most efficient.

  if (!cacheRows(row))
    {
      return false;
    }

  // Now scale and copy the data from the cached row data

  for (int i = 0; i < width; i++, x++)
    {
      // Get the column number in the unscaled row corresponding to the
      // requested x position.  This must be either the exact column or the
      // closest column just before the requested position

      b16_t column = x * m_xScale;

      // Get the color at the position on the first row

      struct rgbcolor_s color1;
      if (!rowColor(m_rowCache[0], column, color1))
        {
          gerr("ERROR: rowColor failed for the first row\n");
          return false;
        }

      // Get the color at the position on the first row

      struct rgbcolor_s color2;
      if (!rowColor(m_rowCache[1], column, color2))
        {
          gerr("ERROR: rowColor failed for the second row\n");
          return false;
        }

      // Check for transparent colors

      bool transparent1;
      bool transparent2;

#if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332
      uint8_t color = RGBTO8(color1.r, color1.g, color1.b);
      transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

      color = RGBTO8(color2.r, color2.g, color2.b);
      transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565
      uint16_t color = RGBTO16(color1.r, color1.g, color1.b);
      transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

      color = RGBTO16(color2.r, color2.g, color2.b);
      transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24 || CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32
      uint32_t color = RGBTO24(color1.r, color1.g, color1.b);
      transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

      color = RGBTO24(color2.r, color2.g, color2.b);
      transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#else
#  error Unsupported, invalid, or undefined color format
#endif

      // Is one of the colors transparent?

      struct rgbcolor_s scaledColor;
      b16_t fraction b16frac(row16);

      if (transparent1 || transparent2)
        {
          // Yes.. don't interpolate within transparent regions or
          // between transparent and opaque regions.

          // Get the color closest to the requested position

          if (fraction < b16HALF)
            {
              scaledColor.r = color1.r;
              scaledColor.g = color1.g;
              scaledColor.b = color1.b;
            }
          else
            {
              scaledColor.r = color2.r;
              scaledColor.g = color2.g;
              scaledColor.b = color2.b;
            }
        }
      else
        {
          // No.. both colors are opaque

          if (!scaleColor(color1, color2, fraction, scaledColor))
            {
              return false;
            }
        }

      // Write the interpolated data to the user buffer

#if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332
      color = RGBTO8(scaledColor.r, scaledColor.g, scaledColor.b);
      *dest++ = color;

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565
      color = RGBTO16(scaledColor.r, scaledColor.g, scaledColor.b);
      *dest++ = color;

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24
      *dest++ = color2.b;
      *dest++ = color2.r;
      *dest++ = color2.g;

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32
      color = RGBTO24(scaledColor.r, scaledColor.g, scaledColor.b);
      *dest++ = color;

#else
#  error Unsupported, invalid, or undefined color format
#endif
    }

  return true;
}

/**
 * Read two rows into the row cache
 *
 * @param row - The row number of the first row to cache
 */

bool CScaledBitmap::cacheRows(unsigned int row)
{
  nxgl_coord_t bitmapWidth  = m_bitmap->getWidth();
  nxgl_coord_t bitmapHeight = m_bitmap->getHeight();

  // A common case is to advance by one row.  In this case, we only
  // need to read one row

  if (row == m_row + 1)
    {
      // Swap rows

      FAR uint8_t *saveRow = m_rowCache[0];
      m_rowCache[0] = m_rowCache[1];
      m_rowCache[1] = saveRow;

      // Save number of the first row that we have in the cache

      m_row = row;

      // Now read the new row into the second row cache buffer

      if (++row >= (unsigned int)bitmapHeight)
        {
          row = bitmapHeight - 1;
        }

      if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[1]))
        {
          gerr("ERROR: Failed to read bitmap row %d\n", row);
          return false;
        }
    }

  // Do we need to read two new rows?  Or do we already have the
  // request row in the cache?

  else if (row != m_row)
    {
      // Read the first row into the cache

      if (row >= (unsigned int)bitmapHeight)
        {
          row = bitmapHeight - 1;
        }

      if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[0]))
        {
          gerr("ERROR: Failed to read bitmap row %d\n", row);
          return false;
        }

      // Save number of the first row that we have in the cache

      m_row = row;

      // Read the next row into the cache

      if (++row >= (unsigned int)bitmapHeight)
        {
          row = bitmapHeight - 1;
        }

      if (!m_bitmap->getRun(0, row, bitmapWidth, m_rowCache[1]))
        {
          gerr("ERROR: Failed to read bitmap row %d\n", row);
          return false;
        }
    }

  return true;
}

/**
 * Given an two RGB colors and a fractional value, return the scaled
 * value between the two colors.
 *
 * @param incolor1 - The first color to be used
 * @param incolor2 - The second color to be used
 * @param fraction - The fractional value
 * @param outcolor - The returned, scaled color
 */

bool CScaledBitmap::scaleColor(FAR const struct rgbcolor_s &incolor1,
                               FAR const struct rgbcolor_s &incolor2,
                               b16_t fraction, FAR struct rgbcolor_s &outcolor)
{
  uint8_t component;
  b16_t red;
  b16_t green;
  b16_t blue;

  // A fraction of < 0.5 would mean to use use mostly color1; a fraction
  // greater than 0.5 would men to use mostly color2

  b16_t remainder = b16ONE - fraction;

  // Interpolate each color value  (converting to b15)

  red   = (b16_t)incolor1.r * remainder + (b16_t)incolor2.r * fraction;
  green = (b16_t)incolor1.g * remainder + (b16_t)incolor2.g * fraction;
  blue  = (b16_t)incolor1.b * remainder + (b16_t)incolor2.b * fraction;

  // Return the integer, interpolated values, clipping to the range of
  // uint8_t

  component  = b16toi(red);
  outcolor.r = component < 256 ? component : 255;

  component  = b16toi(green);
  outcolor.g = component < 256 ? component : 255;

  component  = b16toi(blue);
  outcolor.b = component < 256 ? component : 255;
  return true;
}

/**
 * Given an image row and a non-integer column offset, return the
 * interpolated RGB color value corresponding to that position
 *
 * @param row - The pointer to the row in the row cache to use
 * @param column - The non-integer column offset
 * @param outcolor - The returned, interpolated color
 *
 */

bool CScaledBitmap::rowColor(FAR uint8_t *row, b16_t column,
                             FAR struct rgbcolor_s &outcolor)
{
  // This is the col at or just before the pixel of interest

  nxgl_coord_t col1 = b16toi(column);
  nxgl_coord_t col2 = col1 + 1;

  nxgl_coord_t bitmapWidth  = m_bitmap->getWidth();
  if (col2 >= bitmapWidth)
    {
      col2 = bitmapWidth - 1;
    }

  b16_t fraction = b16frac(column);

  struct rgbcolor_s color1;
  struct rgbcolor_s color2;

  bool transparent1;
  bool transparent2;

#if CONFIG_NXWIDGETS_FMT == FB_FMT_RGB8_332
  uint8_t color = row[col1];
  color1.r = RGB8RED(color);
  color1.g = RGB8GREEN(color);
  color1.b = RGB8BLUE(color);

  transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

  color = row[col2];
  color2.r = RGB8RED(color);
  color2.g = RGB8GREEN(color);
  color2.b = RGB8BLUE(color);

  transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB16_565
  FAR uint16_t *row16 = (FAR uint16_t*)row;
  uint16_t color = row16[col1];
  color1.r = RGB16RED(color);
  color1.g = RGB16GREEN(color);
  color1.b = RGB16BLUE(color);

  transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

  color = row16[col2];
  color2.r = RGB16RED(color);
  color2.g = RGB16GREEN(color);
  color2.b = RGB16BLUE(color);

  transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB24
  unsigned int ndx = 3*col1;
  color1.r = row[ndx+2];
  color1.g = row[ndx+1];
  color1.b = row[ndx];

  uint32_t color = RGBTO24(color1.r, color1.g, color1.b);
  transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

  ndx      = 3*col2;
  color2.r = row[ndx+2];
  color2.g = row[ndx+1];
  color2.b = row[ndx];

  color = RGBTO24(color2.r, color2.g, color2.b);
  transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#elif CONFIG_NXWIDGETS_FMT == FB_FMT_RGB32
  FAR uint32_t *row32 = (FAR uint32_t*)row;
  uint32_t color = row32[col1];
  color1.r = RGB24RED(color);
  color1.g = RGB24GREEN(color);
  color1.b = RGB24BLUE(color);

  transparent1 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

  color    = row32[col2];
  color2.r = RGB24RED(color);
  color2.g = RGB24GREEN(color);
  color2.b = RGB24BLUE(color);

  transparent2 = (color == CONFIG_NXWIDGETS_TRANSPARENT_COLOR);

#else
#  error Unsupported, invalid, or undefined color format
#endif

  // Is one of the colors transparent?

  if (transparent1 || transparent2)
    {
      // Yes.. don't interpolate within transparent regions or
      // between transparent and opaque regions.

      // Return the color closest to the requested position
      //
      // A fraction of < 0.5 would mean to use use mostly color1; a fraction
      // greater than 0.5 would men to use mostly color2

      if (fraction < b16HALF)
        {
          outcolor.r = color1.r;
          outcolor.g = color1.b;
          outcolor.g = color1.g;
        }
      else
        {
          outcolor.r = color2.r;
          outcolor.g = color2.b;
          outcolor.g = color2.g;
        }

      return true;
    }
  else
    {
      // No.. both colors are opaque

      return scaleColor(color1, color2, fraction, outcolor);
    }
}
