<commit_before>/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/skottie/src/effects/Effects.h"

#include "include/core/SkColorFilter.h"
#include "include/effects/SkRuntimeEffect.h"
#include "modules/skottie/src/Adapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"

namespace skottie::internal {

namespace  {

// The contrast effect transfer function can be approximated with the following
// 3rd degree polynomial:
//
//   f(x) = -2πC/3 * x³ + πC * x² + (1 - πC/3) * x
//
// where C is the normalized contrast value [-1..1].
//
// Derivation:
//
//   - start off with sampling the AE contrast effect for various contrast/input values [1]
//
//   - apply cubic polynomial curve fitting to determine best-fit coefficients for given
//     contrast values [2]
//
//   - observations:
//       * negative contrast appears clamped at -0.5 (-50)
//       * a,b coefficients vary linearly vs. contrast
//       * the b coefficient for max contrast (1.0) looks kinda familiar: 3.14757 - coincidence?
//         probably not.  let's run with it: b == πC
//
//   - additionally, we expect the following to hold:
//       * f(0  ) = 0   \     | d = 0
//       * f(1  ) = 1    | => | a = -2b/3
//       * f(0.5) = 0.5 /     | c = 1 - b/3
//
//   - this yields a pretty decent approximation: [3]
//
//
// Note (courtesy of mtklein, reed): [4] seems to yield a closer approximation, but requires
// a more expensive sin
//
//   f(x) = x + a * sin(2πx)/2π
//
// [1] https://www.desmos.com/calculator/oksptqpo8z
// [2] https://www.desmos.com/calculator/oukrf6yahn
// [3] https://www.desmos.com/calculator/ehem0vy3ft
// [4] https://www.desmos.com/calculator/5t4xi10q4v
//
static sk_sp<SkData> make_contrast_coeffs(float contrast) {
    struct { float a, b, c; } coeffs;

    coeffs.b = SK_ScalarPI * contrast;
    coeffs.a = -2 * coeffs.b / 3;
    coeffs.c =  1 - coeffs.b / 3;

    return SkData::MakeWithCopy(&coeffs, sizeof(coeffs));
}

static constexpr char CONTRAST_EFFECT[] = R"(
    uniform half a;
    uniform half b;
    uniform half c;

    void main(inout half4 color) {
        // C' = a*C^3 + b*C^2 + c*C
        color.rgb = ((a*color.rgb + b)*color.rgb + c)*color.rgb;
    }
)";

class BrightnessContrastAdapter final : public DiscardableAdapterBase<BrightnessContrastAdapter,
                                                                      sksg::ExternalColorFilter> {
public:
    BrightnessContrastAdapter(const skjson::ArrayValue& jprops,
                              const AnimationBuilder& abuilder,
                              sk_sp<sksg::RenderNode> layer)
        : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer)))
        , fContrastEffect(std::get<0>(SkRuntimeEffect::Make(SkString(CONTRAST_EFFECT)))) {
        SkASSERT(fContrastEffect);

        enum : size_t {
            kBrightness_Index = 0,
              kContrast_Index = 1,
             kUseLegacy_Index = 2,
        };

        EffectBinder(jprops, abuilder, this)
            .bind(kBrightness_Index, fBrightness)
            .bind(  kContrast_Index, fContrast  )
            .bind( kUseLegacy_Index, fUseLegacy );
    }

private:
    void onSync() override {
        this->node()->setColorFilter(SkScalarRoundToInt(fUseLegacy)
                                        ? this->makeLegacyCF()
                                        : this->makeCF());
    }
<commit_msg>[skottie] More accurate contrast effect option<commit_after>/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/skottie/src/effects/Effects.h"

#include "include/core/SkColorFilter.h"
#include "include/effects/SkRuntimeEffect.h"
#include "modules/skottie/src/Adapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"

namespace skottie::internal {

namespace  {

// The contrast effect transfer function can be approximated with the following
// 3rd degree polynomial:
//
//   f(x) = -2πC/3 * x³ + πC * x² + (1 - πC/3) * x
//
// where C is the normalized contrast value [-1..1].
//
// Derivation:
//
//   - start off with sampling the AE contrast effect for various contrast/input values [1]
//
//   - apply cubic polynomial curve fitting to determine best-fit coefficients for given
//     contrast values [2]
//
//   - observations:
//       * negative contrast appears clamped at -0.5 (-50)
//       * a,b coefficients vary linearly vs. contrast
//       * the b coefficient for max contrast (1.0) looks kinda familiar: 3.14757 - coincidence?
//         probably not.  let's run with it: b == πC
//
//   - additionally, we expect the following to hold:
//       * f(0  ) = 0   \     | d = 0
//       * f(1  ) = 1    | => | a = -2b/3
//       * f(0.5) = 0.5 /     | c = 1 - b/3
//
//   - this yields a pretty decent approximation: [3]
//
//
// Note (courtesy of mtklein, reed): [4] seems to yield a closer approximation, but requires
// a more expensive sin
//
//   f(x) = x + a * sin(2πx)/2π
//
// [1] https://www.desmos.com/calculator/oksptqpo8z
// [2] https://www.desmos.com/calculator/oukrf6yahn
// [3] https://www.desmos.com/calculator/ehem0vy3ft
// [4] https://www.desmos.com/calculator/5t4xi10q4v
//

#ifndef SKOTTIE_ACCURATE_CONTRAST_APPROXIMATION
static sk_sp<SkData> make_contrast_coeffs(float contrast) {
    struct { float a, b, c; } coeffs;

    coeffs.b = SK_ScalarPI * contrast;
    coeffs.a = -2 * coeffs.b / 3;
    coeffs.c =  1 - coeffs.b / 3;

    return SkData::MakeWithCopy(&coeffs, sizeof(coeffs));
}

static constexpr char CONTRAST_EFFECT[] = R"(
    uniform half a;
    uniform half b;
    uniform half c;

    void main(inout half4 color) {
        // C' = a*C^3 + b*C^2 + c*C
        color.rgb = ((a*color.rgb + b)*color.rgb + c)*color.rgb;
    }
)";
#else
// More accurate (but slower) approximation:
//
//   f(x) = x + a * sin(2πx)
//
//   a = -contrast/3π
//
static sk_sp<SkData> make_contrast_coeffs(float contrast) {
    const auto coeff_a = -contrast / (3 * SK_ScalarPI);

    return SkData::MakeWithCopy(&coeff_a, sizeof(coeff_a));
}

static constexpr char CONTRAST_EFFECT[] = R"(
    uniform half a;

    void main(inout half4 color) {
        color.rgb += a * sin(color.rgb * 6.283185);
    }
)";

#endif

class BrightnessContrastAdapter final : public DiscardableAdapterBase<BrightnessContrastAdapter,
                                                                      sksg::ExternalColorFilter> {
public:
    BrightnessContrastAdapter(const skjson::ArrayValue& jprops,
                              const AnimationBuilder& abuilder,
                              sk_sp<sksg::RenderNode> layer)
        : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer)))
        , fContrastEffect(std::get<0>(SkRuntimeEffect::Make(SkString(CONTRAST_EFFECT)))) {
        SkASSERT(fContrastEffect);

        enum : size_t {
            kBrightness_Index = 0,
              kContrast_Index = 1,
             kUseLegacy_Index = 2,
        };

        EffectBinder(jprops, abuilder, this)
            .bind(kBrightness_Index, fBrightness)
            .bind(  kContrast_Index, fContrast  )
            .bind( kUseLegacy_Index, fUseLegacy );
    }

private:
    void onSync() override {
        this->node()->setColorFilter(SkScalarRoundToInt(fUseLegacy)
                                        ? this->makeLegacyCF()
                                        : this->makeCF());
    }

{'fraction_non_alphanumeric': 0.1314865392118611, 'fraction_numerical': 0.01859799713876967, 'mean_word_length': 2.280716723549488, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 22, 'https://': 8, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3136090', 'n_tokens_mistral': 2630, 'n_tokens_neox': 2524, 'n_words': 617}