Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c9a6886 authored by Jorge Betancourt's avatar Jorge Betancourt
Browse files

add runtime color filter support to platform

Flag: com.android.graphics.hwui.flags.runtime_color_filters_blenders

Test: atest CtsUiRenderingTestCases:RuntimeColorFilterTests
Bug: b/358126864

Change-Id: If569957489f5352455c419074219125f6e9cc43e
parent 94a0c87d
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -17318,6 +17318,25 @@ package android.graphics {
    method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
  }
  @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeColorFilter extends android.graphics.ColorFilter {
    ctor public RuntimeColorFilter(@NonNull String);
    method public void setColorUniform(@NonNull String, @ColorInt int);
    method public void setColorUniform(@NonNull String, @ColorLong long);
    method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
    method public void setFloatUniform(@NonNull String, float);
    method public void setFloatUniform(@NonNull String, float, float);
    method public void setFloatUniform(@NonNull String, float, float, float);
    method public void setFloatUniform(@NonNull String, float, float, float, float);
    method public void setFloatUniform(@NonNull String, @NonNull float[]);
    method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
    method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
    method public void setIntUniform(@NonNull String, int);
    method public void setIntUniform(@NonNull String, int, int);
    method public void setIntUniform(@NonNull String, int, int, int);
    method public void setIntUniform(@NonNull String, int, int, int, int);
    method public void setIntUniform(@NonNull String, @NonNull int[]);
  }
  public class RuntimeShader extends android.graphics.Shader {
    ctor public RuntimeShader(@NonNull String);
    method public void setColorUniform(@NonNull String, @ColorInt int);
+305 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * 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.
 */

package android.graphics;

import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;

import com.android.graphics.hwui.flags.Flags;


/**
 * <p>A {@link RuntimeColorFilter} calculates a per-pixel color based on the output of a user
 *  * defined Android Graphics Shading Language (AGSL) function.</p>
 *
 * <p>This AGSL function takes in an input color to be operated on. This color is in sRGB and the
 *  * output is also interpreted as sRGB. The AGSL function signature expects a single input
 *  * of color (packed as a half4 or float4 or vec4).</p>
 *
 * <pre class="prettyprint">
 * vec4 main(half4 in_color);
 * </pre>
 */
@FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
public class RuntimeColorFilter extends ColorFilter {

    private String mAgsl;

    /**
     * Creates a new RuntimeColorFilter.
     *
     * @param agsl The text of AGSL color filter program to run.
     */
    public RuntimeColorFilter(@NonNull String agsl) {
        if (agsl == null) {
            throw new NullPointerException("RuntimeColorFilter requires a non-null AGSL string");
        }
        mAgsl = agsl;
        // call to parent class to register native RuntimeColorFilter
        // TODO: find way to get super class to create native instance without requiring the storage
        // of agsl string
        getNativeInstance();

    }
    /**
     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the color uniform declared in the AGSL program
     * @param color the provided sRGB color
     */
    public void setColorUniform(@NonNull String uniformName, @ColorInt int color) {
        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
    }

    /**
     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the color uniform declared in the AGSL program
     * @param color the provided sRGB color
     */
    public void setColorUniform(@NonNull String uniformName, @ColorLong long color) {
        Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
        setUniform(uniformName, exSRGB.getComponents(), true);
    }

    /**
     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the color uniform declared in the AGSL program
     * @param color the provided sRGB color
     */
    public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
        if (color == null) {
            throw new NullPointerException("The color parameter must not be null");
        }
        Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
        setUniform(uniformName, exSRGB.getComponents(), true);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than a float or
     * float[1] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setFloatUniform(@NonNull String uniformName, float value) {
        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than a vec2 or
     * float[2] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than a vec3 or
     * float[3] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
            float value3) {
        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);

    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than a vec4 or
     * float[4] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
            float value3, float value4) {
        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than a float
     * (for N=1), vecN, or float[N] where N is the length of the values param then an
     * IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
        setUniform(uniformName, values, false);
    }

    private void setFloatUniform(@NonNull String uniformName, float value1, float value2,
            float value3, float value4, int count) {
        if (uniformName == null) {
            throw new NullPointerException("The uniformName parameter must not be null");
        }
        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
                count);
    }

    private void setUniform(@NonNull String uniformName, @NonNull float[] values, boolean isColor) {
        if (uniformName == null) {
            throw new NullPointerException("The uniformName parameter must not be null");
        }
        if (values == null) {
            throw new NullPointerException("The uniform values parameter must not be null");
        }
        nativeUpdateUniforms(getNativeInstance(), uniformName, values, isColor);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than an int or int[1]
     * then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setIntUniform(@NonNull String uniformName, int value) {
        setIntUniform(uniformName, value, 0, 0, 0, 1);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than an ivec2 or
     * int[2] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
        setIntUniform(uniformName, value1, value2, 0, 0, 2);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than an ivec3 or
     * int[3] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
        setIntUniform(uniformName, value1, value2, value3, 0, 3);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than an ivec4 or
     * int[4] then an IllegalArgumentException is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setIntUniform(@NonNull String uniformName, int value1, int value2,
            int value3, int value4) {
        setIntUniform(uniformName, value1, value2, value3, value4, 4);
    }

    /**
     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
     * uniform with that name or if the uniform is declared with a type other than an int (for N=1),
     * ivecN, or int[N] where N is the length of the values param then an IllegalArgumentException
     * is thrown.
     *
     * @param uniformName name matching the uniform declared in the AGSL program
     */
    public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
        if (uniformName == null) {
            throw new NullPointerException("The uniformName parameter must not be null");
        }
        if (values == null) {
            throw new NullPointerException("The uniform values parameter must not be null");
        }
        nativeUpdateUniforms(getNativeInstance(), uniformName, values);
    }

    private void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3,
            int value4, int count) {
        if (uniformName == null) {
            throw new NullPointerException("The uniformName parameter must not be null");
        }
        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
                count);
    }

    /**
     * Assigns the uniform shader to the provided shader parameter.  If the shader program does not
     * have a uniform shader with that name then an IllegalArgumentException is thrown.
     *
     * @param shaderName name matching the uniform declared in the AGSL program
     * @param shader shader passed into the AGSL program for sampling
     */
    public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
        if (shaderName == null) {
            throw new NullPointerException("The shaderName parameter must not be null");
        }
        if (shader == null) {
            throw new NullPointerException("The shader parameter must not be null");
        }
        nativeUpdateChild(getNativeInstance(), shaderName, shader.getNativeInstance());
    }

    /**
     * Assigns the uniform color filter to the provided color filter parameter.  If the shader
     * program does not have a uniform color filter with that name then an IllegalArgumentException
     * is thrown.
     *
     * @param filterName name matching the uniform declared in the AGSL program
     * @param colorFilter filter passed into the AGSL program for sampling
     */
    public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
        if (filterName == null) {
            throw new NullPointerException("The filterName parameter must not be null");
        }
        if (colorFilter == null) {
            throw new NullPointerException("The colorFilter parameter must not be null");
        }
        nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
    }

    /** @hide */
    @Override
    protected long createNativeInstance() {
        return nativeCreateRuntimeColorFilter(mAgsl);
    }

    private static native long nativeCreateRuntimeColorFilter(String agsl);
    private static native void nativeUpdateUniforms(
            long colorFilter, String uniformName, float[] uniforms, boolean isColor);
    private static native void nativeUpdateUniforms(
            long colorFilter, String uniformName, float value1, float value2, float value3,
            float value4, int count);
    private static native void nativeUpdateUniforms(
            long colorFilter, String uniformName, int[] uniforms);
    private static native void nativeUpdateUniforms(
            long colorFilter, String uniformName, int value1, int value2, int value3,
            int value4, int count);
    private static native void nativeUpdateChild(long colorFilter, String childName, long child);

}
+1 −0
Original line number Diff line number Diff line
@@ -384,6 +384,7 @@ cc_defaults {
        "jni/ScopedParcel.cpp",
        "jni/Shader.cpp",
        "jni/RenderEffect.cpp",
        "jni/RuntimeEffectUtils.cpp",
        "jni/Typeface.cpp",
        "jni/Utils.cpp",
        "jni/YuvToJpegEncoder.cpp",
+31 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <memory>

#include "GraphicsJNI.h"
#include "RuntimeEffectUtils.h"
#include "SkColorFilter.h"

namespace android {
@@ -113,6 +114,36 @@ private:
    std::vector<float> mMatrix;
};

class RuntimeColorFilter : public ColorFilter {
public:
    RuntimeColorFilter(SkRuntimeEffectBuilder* builder) : mBuilder(builder) {}

    void updateUniforms(JNIEnv* env, const char* name, const float vals[], int count,
                        bool isColor) {
        UpdateFloatUniforms(env, mBuilder, name, vals, count, isColor);
        discardInstance();
    }

    void updateUniforms(JNIEnv* env, const char* name, const int vals[], int count) {
        UpdateIntUniforms(env, mBuilder, name, vals, count);
        discardInstance();
    }

    void updateChild(JNIEnv* env, const char* name, SkFlattenable* childEffect) {
        UpdateChild(env, mBuilder, name, childEffect);
        discardInstance();
    }

private:
    sk_sp<SkColorFilter> createInstance() override {
        // TODO: throw error if null
        return mBuilder->makeColorFilter();
    }

private:
    SkRuntimeEffectBuilder* mBuilder;
};

}  // namespace uirenderer
}  // namespace android

+92 −1
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@
#include "ColorFilter.h"

#include "GraphicsJNI.h"
#include "RuntimeEffectUtils.h"
#include "SkBlendMode.h"
#include "include/effects/SkRuntimeEffect.h"

namespace android {

@@ -89,6 +91,78 @@ public:
            filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
        }
    }

    static jlong RuntimeColorFilter_createColorFilter(JNIEnv* env, jobject, jstring agsl) {
        ScopedUtfChars strSksl(env, agsl);
        auto result = SkRuntimeEffect::MakeForColorFilter(SkString(strSksl.c_str()),
                                                          SkRuntimeEffect::Options{});
        if (result.effect.get() == nullptr) {
            doThrowIAE(env, result.errorText.c_str());
            return 0;
        }
        auto builder = new SkRuntimeEffectBuilder(std::move(result.effect));
        auto* runtimeColorFilter = new RuntimeColorFilter(builder);
        runtimeColorFilter->incStrong(nullptr);
        return static_cast<jlong>(reinterpret_cast<uintptr_t>(runtimeColorFilter));
    }

    static void RuntimeColorFilter_updateUniformsFloatArray(JNIEnv* env, jobject,
                                                            jlong colorFilterPtr,
                                                            jstring uniformName,
                                                            jfloatArray uniforms,
                                                            jboolean isColor) {
        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
        ScopedUtfChars name(env, uniformName);
        AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
        if (filter) {
            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length(),
                                   isColor);
        }
    }

    static void RuntimeColorFilter_updateUniformsFloats(JNIEnv* env, jobject, jlong colorFilterPtr,
                                                        jstring uniformName, jfloat value1,
                                                        jfloat value2, jfloat value3, jfloat value4,
                                                        jint count) {
        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
        ScopedUtfChars name(env, uniformName);
        const float values[4] = {value1, value2, value3, value4};
        if (filter) {
            filter->updateUniforms(env, name.c_str(), values, count, false);
        }
    }

    static void RuntimeColorFilter_updateUniformsIntArray(JNIEnv* env, jobject,
                                                          jlong colorFilterPtr, jstring uniformName,
                                                          jintArray uniforms) {
        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
        ScopedUtfChars name(env, uniformName);
        AutoJavaIntArray autoValues(env, uniforms, 0);
        if (filter) {
            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length());
        }
    }

    static void RuntimeColorFilter_updateUniformsInts(JNIEnv* env, jobject, jlong colorFilterPtr,
                                                      jstring uniformName, jint value1, jint value2,
                                                      jint value3, jint value4, jint count) {
        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
        ScopedUtfChars name(env, uniformName);
        const int values[4] = {value1, value2, value3, value4};
        if (filter) {
            filter->updateUniforms(env, name.c_str(), values, count);
        }
    }

    static void RuntimeColorFilter_updateChild(JNIEnv* env, jobject, jlong colorFilterPtr,
                                               jstring childName, jlong childPtr) {
        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
        ScopedUtfChars name(env, childName);
        auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
        if (filter && child) {
            filter->updateChild(env, name.c_str(), child);
        }
    }
};

static const JNINativeMethod colorfilter_methods[] = {
@@ -107,6 +181,20 @@ static const JNINativeMethod colormatrix_methods[] = {
        {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
        {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};

static const JNINativeMethod runtime_color_filter_methods[] = {
        {"nativeCreateRuntimeColorFilter", "(Ljava/lang/String;)J",
         (void*)ColorFilterGlue::RuntimeColorFilter_createColorFilter},
        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloatArray},
        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloats},
        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsIntArray},
        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
        {"nativeUpdateChild", "(JLjava/lang/String;J)V",
         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};

int register_android_graphics_ColorFilter(JNIEnv* env) {
    android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
                                  NELEM(colorfilter_methods));
@@ -118,6 +206,9 @@ int register_android_graphics_ColorFilter(JNIEnv* env) {
                                  NELEM(lighting_methods));
    android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
                                  colormatrix_methods, NELEM(colormatrix_methods));
    android::RegisterMethodsOrDie(env, "android/graphics/RuntimeColorFilter",
                                  runtime_color_filter_methods,
                                  NELEM(runtime_color_filter_methods));

    return 0;
}
Loading