// Aseprite Render Library
// Copyright (c) 2019-2022  Igara Studio S.A
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "render/error_diffusion.h"

#include "gfx/hsl.h"
#include "gfx/rgb.h"

#include <algorithm>

namespace render {

ErrorDiffusionDither::ErrorDiffusionDither(int transparentIndex)
  : m_transparentIndex(transparentIndex)
{
}

void ErrorDiffusionDither::start(const doc::Image* srcImage,
                                 doc::Image* dstImage,
                                 const double factor)
{
  m_srcImage = srcImage;
  m_width = 2 + srcImage->width();
  for (int i = 0; i < kChannels; ++i)
    m_err[i].resize(m_width * 2, 0);
  m_lastY = -1;
  m_factor = int(factor * 100.0);
}

void ErrorDiffusionDither::finish()
{
}

doc::color_t ErrorDiffusionDither::ditherRgbToIndex2D(const int x,
                                                      const int y,
                                                      const doc::RgbMap* rgbmap,
                                                      const doc::Palette* palette)
{
  if (y != m_lastY) {
    for (int i = 0; i < kChannels; ++i) {
      int* row0 = &m_err[i][0];
      int* row1 = row0 + m_width;
      int* end1 = row1 + m_width;
      std::copy(row1, end1, row0);
      std::fill(row1, end1, 0);
    }
    m_lastY = y;
  }

  doc::color_t color = doc::get_pixel_fast<doc::RgbTraits>(m_srcImage, x, y);

  // Get RGB values + quatization error
  int v[kChannels] = { doc::rgba_getr(color),
                       doc::rgba_getg(color),
                       doc::rgba_getb(color),
                       doc::rgba_geta(color) };
  for (int i = 0; i < kChannels; ++i) {
    v[i] += m_err[i][x + 1];
    v[i] = std::clamp(v[i], 0, 255);
  }

  const doc::color_t index = (rgbmap ?
                                rgbmap->mapColor(v[0], v[1], v[2], v[3]) :
                                palette->findBestfit(v[0], v[1], v[2], v[3], m_transparentIndex));

  doc::color_t palColor = palette->getEntry(index);
  if (m_transparentIndex == index || doc::rgba_geta(palColor) == 0) {
    // "color" without alpha
    palColor = (color & doc::rgba_rgb_mask);
  }

  const int quantError[kChannels] = { v[0] - doc::rgba_getr(palColor),
                                      v[1] - doc::rgba_getg(palColor),
                                      v[2] - doc::rgba_getb(palColor),
                                      v[3] - doc::rgba_geta(palColor) };

  // TODO using Floyd-Steinberg matrix here but it should be configurable
  for (int i = 0; i < kChannels; ++i) {
    int* err = &m_err[i][x];
    const int q = quantError[i] * m_factor / 100;
    const int a = q * 7 / 16;
    const int b = q * 3 / 16;
    const int c = q * 5 / 16;
    const int d = q * 1 / 16;

    if (y & 1) {
      err[0] += a;
      err[m_width + 2] += b;
      err[m_width + 1] += c;
      err[m_width] += d;
    }
    else {
      err[+2] += a;
      err[m_width] += b;
      err[m_width + 1] += c;
      err[m_width + 2] += d;
    }
  }

  return index;
}

} // namespace render
