// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This file is automatically generated. Do not modify it.

import 'dart:math';

import 'math_utils.dart';

/// Color science utilities.
///
/// Utility methods for color science constants and color space
/// conversions that aren't HCT or CAM16.
class ColorUtils {
  static const List<List<double>> _srgbToXyz = <List<double>>[
    <double>[0.41233895, 0.35762064, 0.18051042],
    <double>[0.2126, 0.7152, 0.0722],
    <double>[0.01932141, 0.11916382, 0.95034478],
  ];

  static const List<List<double>> _xyzToSrgb = <List<double>>[
    <double>[
      3.2413774792388685,
      -1.5376652402851851,
      -0.49885366846268053,
    ],
    <double>[
      -0.9691452513005321,
      1.8758853451067872,
      0.04156585616912061,
    ],
    <double>[
      0.05562093689691305,
      -0.20395524564742123,
      1.0571799111220335,
    ],
  ];

  static const List<double> _whitePointD65 = <double>[95.047, 100.0, 108.883];

  /// Converts a color from RGB components to ARGB format.
  static int argbFromRgb(int red, int green, int blue) {
    return 255 << 24 | (red & 255) << 16 | (green & 255) << 8 | blue & 255;
  }

  /// Converts a color from linear RGB components to ARGB format.
  static int argbFromLinrgb(List<double> linrgb) {
    final int r = delinearized(linrgb[0]);
    final int g = delinearized(linrgb[1]);
    final int b = delinearized(linrgb[2]);
    return argbFromRgb(r, g, b);
  }

  /// Returns the alpha component of a color in ARGB format.
  static int alphaFromArgb(int argb) {
    return argb >> 24 & 255;
  }

  /// Returns the red component of a color in ARGB format.
  static int redFromArgb(int argb) {
    return argb >> 16 & 255;
  }

  /// Returns the green component of a color in ARGB format.
  static int greenFromArgb(int argb) {
    return argb >> 8 & 255;
  }

  /// Returns the blue component of a color in ARGB format.
  static int blueFromArgb(int argb) {
    return argb & 255;
  }

  /// Returns whether a color in ARGB format is opaque.
  static bool isOpaque(int argb) {
    return alphaFromArgb(argb) >= 255;
  }

  /// Converts a color from ARGB to XYZ.
  static int argbFromXyz(double x, double y, double z) {
    const List<List<double>> matrix = _xyzToSrgb;
    final double linearR =
        matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
    final double linearG =
        matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
    final double linearB =
        matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
    final int r = delinearized(linearR);
    final int g = delinearized(linearG);
    final int b = delinearized(linearB);
    return argbFromRgb(r, g, b);
  }

  /// Converts a color from XYZ to ARGB.
  static List<double> xyzFromArgb(int argb) {
    final double r = linearized(redFromArgb(argb));
    final double g = linearized(greenFromArgb(argb));
    final double b = linearized(blueFromArgb(argb));
    return MathUtils.matrixMultiply(<double>[r, g, b], _srgbToXyz);
  }

  /// Converts a color represented in Lab color space into an ARGB
  /// integer.
  static int argbFromLab(double l, double a, double b) {
    const List<double> whitePoint = _whitePointD65;
    final double fy = (l + 16.0) / 116.0;
    final double fx = a / 500.0 + fy;
    final double fz = fy - b / 200.0;
    final double xNormalized = _labInvf(fx);
    final double yNormalized = _labInvf(fy);
    final double zNormalized = _labInvf(fz);
    final double x = xNormalized * whitePoint[0];
    final double y = yNormalized * whitePoint[1];
    final double z = zNormalized * whitePoint[2];
    return argbFromXyz(x, y, z);
  }

  /// Converts a color from ARGB representation to L*a*b*
  /// representation.
  ///
  /// [argb] the ARGB representation of a color
  /// Returns a Lab object representing the color
  static List<double> labFromArgb(int argb) {
    final double linearR = linearized(redFromArgb(argb));
    final double linearG = linearized(greenFromArgb(argb));
    final double linearB = linearized(blueFromArgb(argb));
    const List<List<double>> matrix = _srgbToXyz;
    final double x = matrix[0][0] * linearR +
        matrix[0][1] * linearG +
        matrix[0][2] * linearB;
    final double y = matrix[1][0] * linearR +
        matrix[1][1] * linearG +
        matrix[1][2] * linearB;
    final double z = matrix[2][0] * linearR +
        matrix[2][1] * linearG +
        matrix[2][2] * linearB;
    const List<double> whitePoint = _whitePointD65;
    final double xNormalized = x / whitePoint[0];
    final double yNormalized = y / whitePoint[1];
    final double zNormalized = z / whitePoint[2];
    final double fx = _labF(xNormalized);
    final double fy = _labF(yNormalized);
    final double fz = _labF(zNormalized);
    final double l = 116.0 * fy - 16;
    final double a = 500.0 * (fx - fy);
    final double b = 200.0 * (fy - fz);
    return <double>[l, a, b];
  }

  /// Converts an L* value to an ARGB representation.
  ///
  /// [lstar] L* in L*a*b*
  /// Returns ARGB representation of grayscale color with lightness
  /// matching L*
  static int argbFromLstar(double lstar) {
    final double y = yFromLstar(lstar);
    final int component = delinearized(y);
    return argbFromRgb(component, component, component);
  }

  /// Computes the L* value of a color in ARGB representation.
  ///
  /// [argb] ARGB representation of a color
  /// Returns L*, from L*a*b*, coordinate of the color
  static double lstarFromArgb(int argb) {
    final double y = xyzFromArgb(argb)[1];
    return 116.0 * _labF(y / 100.0) - 16.0;
  }

  /// Converts an L* value to a Y value.
  ///
  /// L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
  ///
  /// L* measures perceptual luminance, a linear scale. Y in XYZ
  /// measures relative luminance, a logarithmic scale.
  ///
  /// [lstar] L* in L*a*b*
  /// Returns Y in XYZ
  static double yFromLstar(double lstar) {
    return 100.0 * _labInvf((lstar + 16.0) / 116.0);
  }

  /// Converts a Y value to an L* value.
  ///
  /// L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
  ///
  /// L* measures perceptual luminance, a linear scale. Y in XYZ
  /// measures relative luminance, a logarithmic scale.
  ///
  /// [y] Y in XYZ
  /// Returns L* in L*a*b*
  static double lstarFromY(double y) {
    return _labF(y / 100.0) * 116.0 - 16.0;
  }

  /// Linearizes an RGB component.
  ///
  /// [rgbComponent] 0 <= rgb_component <= 255, represents R/G/B
  /// channel
  /// Returns 0.0 <= output <= 100.0, color channel converted to
  /// linear RGB space
  static double linearized(int rgbComponent) {
    final double normalized = rgbComponent / 255.0;
    if (normalized <= 0.040449936) {
      return normalized / 12.92 * 100.0;
    } else {
      return pow((normalized + 0.055) / 1.055, 2.4).toDouble() * 100.0;
    }
  }

  /// Delinearizes an RGB component.
  ///
  /// [rgbComponent] 0.0 <= rgb_component <= 100.0, represents linear
  /// R/G/B channel
  /// Returns 0 <= output <= 255, color channel converted to regular
  /// RGB space
  static int delinearized(double rgbComponent) {
    final double normalized = rgbComponent / 100.0;
    double delinearized = 0.0;
    if (normalized <= 0.0031308) {
      delinearized = normalized * 12.92;
    } else {
      delinearized = 1.055 * pow(normalized, 1.0 / 2.4).toDouble() - 0.055;
    }
    return MathUtils.clampInt(0, 255, (delinearized * 255.0).round());
  }

  /// Returns the standard white point; white on a sunny day.
  ///
  /// Returns The white point
  static List<double> whitePointD65() {
    return _whitePointD65;
  }

  static double _labF(double t) {
    const double e = 216.0 / 24389.0;
    const double kappa = 24389.0 / 27.0;
    if (t > e) {
      return pow(t, 1.0 / 3.0).toDouble();
    } else {
      return (kappa * t + 16) / 116;
    }
  }

  static double _labInvf(double ft) {
    const double e = 216.0 / 24389.0;
    const double kappa = 24389.0 / 27.0;
    final double ft3 = ft * ft * ft;
    if (ft3 > e) {
      return ft3;
    } else {
      return (116 * ft - 16) / kappa;
    }
  }
}
