// 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.

#include "flutter/display_list/effects/dl_color_filter.h"

#include "flutter/display_list/dl_color.h"

namespace flutter {

std::shared_ptr<DlColorFilter> DlBlendColorFilter::Make(DlColor color,
                                                        DlBlendMode mode) {
  switch (mode) {
    case DlBlendMode::kDst: {
      return nullptr;
    }
    case DlBlendMode::kSrcOver: {
      if (color.isTransparent()) {
        return nullptr;
      }
      if (color.isOpaque()) {
        mode = DlBlendMode::kSrc;
      }
      break;
    }
    case DlBlendMode::kDstOver:
    case DlBlendMode::kDstOut:
    case DlBlendMode::kSrcATop:
    case DlBlendMode::kXor:
    case DlBlendMode::kDarken: {
      if (color.isTransparent()) {
        return nullptr;
      }
      break;
    }
    case DlBlendMode::kDstIn: {
      if (color.isOpaque()) {
        return nullptr;
      }
      break;
    }
    default:
      break;
  }
  return std::make_shared<DlBlendColorFilter>(color, mode);
}

bool DlBlendColorFilter::modifies_transparent_black() const {
  switch (mode_) {
    // These modes all act like kSrc when the dest is all 0s.
    // So they modify transparent black when the src color is
    // not transparent.
    case DlBlendMode::kSrc:
    case DlBlendMode::kSrcOver:
    case DlBlendMode::kDstOver:
    case DlBlendMode::kSrcOut:
    case DlBlendMode::kDstATop:
    case DlBlendMode::kXor:
    case DlBlendMode::kPlus:
    case DlBlendMode::kScreen:
    case DlBlendMode::kOverlay:
    case DlBlendMode::kDarken:
    case DlBlendMode::kLighten:
    case DlBlendMode::kColorDodge:
    case DlBlendMode::kColorBurn:
    case DlBlendMode::kHardLight:
    case DlBlendMode::kSoftLight:
    case DlBlendMode::kDifference:
    case DlBlendMode::kExclusion:
    case DlBlendMode::kMultiply:
    case DlBlendMode::kHue:
    case DlBlendMode::kSaturation:
    case DlBlendMode::kColor:
    case DlBlendMode::kLuminosity:
      return !color_.isTransparent();

    // These modes are all like kDst when the dest is all 0s.
    // So they never modify transparent black.
    case DlBlendMode::kClear:
    case DlBlendMode::kDst:
    case DlBlendMode::kSrcIn:
    case DlBlendMode::kDstIn:
    case DlBlendMode::kDstOut:
    case DlBlendMode::kSrcATop:
    case DlBlendMode::kModulate:
      return false;
  }
}

bool DlBlendColorFilter::can_commute_with_opacity() const {
  switch (mode_) {
    case DlBlendMode::kClear:
    case DlBlendMode::kDst:
    case DlBlendMode::kSrcIn:
    case DlBlendMode::kDstIn:
    case DlBlendMode::kDstOut:
    case DlBlendMode::kSrcATop:
    case DlBlendMode::kModulate:
      return true;

    case DlBlendMode::kSrc:
    case DlBlendMode::kSrcOver:
    case DlBlendMode::kDstOver:
    case DlBlendMode::kSrcOut:
    case DlBlendMode::kDstATop:
    case DlBlendMode::kXor:
    case DlBlendMode::kPlus:
    case DlBlendMode::kScreen:
    case DlBlendMode::kOverlay:
    case DlBlendMode::kDarken:
    case DlBlendMode::kLighten:
    case DlBlendMode::kColorDodge:
    case DlBlendMode::kColorBurn:
    case DlBlendMode::kHardLight:
    case DlBlendMode::kSoftLight:
    case DlBlendMode::kDifference:
    case DlBlendMode::kExclusion:
    case DlBlendMode::kMultiply:
    case DlBlendMode::kHue:
    case DlBlendMode::kSaturation:
    case DlBlendMode::kColor:
    case DlBlendMode::kLuminosity:
      return color_.isTransparent();
  }
}

std::shared_ptr<DlColorFilter> DlMatrixColorFilter::Make(
    const float matrix[20]) {
  float product = 0;
  for (int i = 0; i < 20; i++) {
    product *= matrix[i];
  }
  // If any of the elements of the matrix are infinity or NaN, then
  // |product| will be NaN, otherwise 0.
  if (product == 0) {
    return std::make_shared<DlMatrixColorFilter>(matrix);
  }
  return nullptr;
}

bool DlMatrixColorFilter::modifies_transparent_black() const {
  // Values are considered in non-premultiplied form when the matrix is
  // applied, but we only care about this answer for whether it leaves
  // an incoming color with a transparent alpha as transparent on output.
  // Thus, we only need to consider the alpha part of the matrix equation,
  // which is the last row. Since the incoming alpha value is 0, the last
  // equation ends up becoming A' = matrix_[19]. Negative results will be
  // clamped to the range [0,1] so we only care about positive values.
  // Non-finite values are clamped to a zero alpha.
  return (SkScalarIsFinite(matrix_[19]) && matrix_[19] > 0);
}

bool DlMatrixColorFilter::can_commute_with_opacity() const {
  // We need to check if:
  //   filter(color) * opacity == filter(color * opacity).
  //
  // filter(RGBA) = R' = [ R*m[ 0] + G*m[ 1] + B*m[ 2] + A*m[ 3] + m[ 4] ]
  //                G' = [ R*m[ 5] + G*m[ 6] + B*m[ 7] + A*m[ 8] + m[ 9] ]
  //                B' = [ R*m[10] + G*m[11] + B*m[12] + A*m[13] + m[14] ]
  //                A' = [ R*m[15] + G*m[16] + B*m[17] + A*m[18] + m[19] ]
  //
  // Applying the opacity only affects the alpha value since the operations
  // are performed on non-premultiplied colors. (If the data is stored in
  // premultiplied form, though, there may be rounding errors due to
  // premul->unpremul->premul conversions.)

  // We test for the successful cases and return false if they fail so that
  // we fail and return false if any matrix values are NaN.

  // If any of the alpha column are non-zero then the prior alpha affects
  // the result color, so applying opacity before the filter will change
  // the incoming alpha and therefore the colors that are produced.
  if (!(matrix_[3] == 0 &&    // A does not affect R'
        matrix_[8] == 0 &&    // A does not affect G'
        matrix_[13] == 0)) {  // A does not affect B'
    return false;
  }

  // Similarly, if any of the alpha row are non-zero then the prior colors
  // affect the result alpha in a way that prevents opacity from commuting
  // through the filter operation.
  if (!(matrix_[15] == 0 &&   // R does not affect A'
        matrix_[16] == 0 &&   // G does not affect A'
        matrix_[17] == 0 &&   // B does not affect A'
        matrix_[19] == 0)) {  // A' is not offset by an absolute value
    return false;
  }

  return true;
}

const std::shared_ptr<DlSrgbToLinearGammaColorFilter>
    DlSrgbToLinearGammaColorFilter::instance =
        std::make_shared<DlSrgbToLinearGammaColorFilter>();

const std::shared_ptr<DlLinearToSrgbGammaColorFilter>
    DlLinearToSrgbGammaColorFilter::instance =
        std::make_shared<DlLinearToSrgbGammaColorFilter>();

}  // namespace flutter
