// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef DITHERING_GLSL_
#define DITHERING_GLSL_

#include <impeller/types.glsl>

/// The dithering rate, which is 1.0 / 64.0, or 0.015625.
const float kDitherRate = 1.0 / 64.0;

/// Returns the closest color to the input color using 8x8 ordered dithering.
///
/// Ordered dithering divides the output into a grid of cells, and then assigns
/// a different threshold to each cell. The threshold is used to determine if
/// the color should be rounded up (white) or down (black).
//
/// This technique was chosen mostly because Skia also uses it:
/// https://github.com/google/skia/blob/f9de059517a6f58951510fc7af0cba21e13dd1a8/src/opts/SkRasterPipeline_opts.h#L1717
///
/// See also:
/// - https://en.wikipedia.org/wiki/Ordered_dithering
/// - https://surma.dev/things/ditherpunk/
/// - https://shader-tutorial.dev/advanced/color-banding-dithering/
vec4 IPOrderedDither8x8(vec4 color, vec2 dest) {
  // Get the x and y coordinates of the pixel in the 8x8 grid.
  uint x = uint(dest.x) % 8;
  uint y = uint(dest.y);
  y ^= x;

  // Get the dither value from the matrix.
  uint m = (y & 1) << 5 |  //
           (x & 1) << 4 |  //
           (y & 2) << 2 |  //
           (x & 2) << 1 |  //
           (y & 4) >> 1 |  //
           (x & 4) >> 2;   //

  // Scale that dither to [0,1), then (-0.5,+0.5), here using 63/128 = 0.4921875
  // as 0.5-epsilon. We want to make sure our dither is less than 0.5 in either
  // direction to keep exact values like 0 and 1 unchanged after rounding.
  float dither = float(m) * (2.0 / 128.0) - (63.0 / 128.0);

  // Apply the dither to the color.
  color.rgb += dither * kDitherRate;

  // Clamp the color values to [0,1].
  color.rgb = clamp(color.rgb, 0.0, 1.0);

  return color;
}

#endif
