Commit 2c27cd82 by leonwind Committed by Marc Baechinger

Add HSL Adjustments to the effects module.

PiperOrigin-RevId: 476144167
parent afd829e5
#version 100
// Copyright 2022 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.
// ES 2 fragment shader that samples from a (non-external) texture with
// uTexSampler. It then converts the RGB color input into HSL and adjusts
// the Hue, Saturation, and Lightness and converts it then back to RGB.
// We use the algorithm based on the work by Sam Hocevar, which optimizes
// for an efficient branchless RGB <-> HSL conversion. A blog post is
// at https://www.chilliant.com/rgb2hsv.html and it is further explained at
// http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
precision highp float;
uniform sampler2D uTexSampler;
// uHueAdjustmentDegrees, uSaturationAdjustment, and uLightnessAdjustment
// are normalized to the unit interval [0, 1].
uniform float uHueAdjustmentDegrees;
uniform float uSaturationAdjustment;
uniform float uLightnessAdjustment;
varying vec2 vTexSamplingCoord;
const float epsilon = 1e-10;
vec3 rgbToHcv(vec3 rgb) {
vec4 p = (rgb.g < rgb.b)
? vec4(rgb.bg, -1.0, 2.0 / 3.0)
: vec4(rgb.gb, 0.0, -1.0 / 3.0);
vec4 q = (rgb.r < p.x)
? vec4(p.xyw, rgb.r)
: vec4(rgb.r, p.yzx);
float c = q.x - min(q.w, q.y);
float h = abs((q.w - q.y) / (6.0 * c + epsilon) + q.z);
return vec3(h, c, q.x);
}
vec3 rgbToHsl(vec3 rgb) {
vec3 hcv = rgbToHcv(rgb);
float l = hcv.z - hcv.y * 0.5;
float s = hcv.y / (1.0 - abs(l * 2.0 - 1.0) + epsilon);
return vec3(hcv.x, s, l);
}
vec3 hueToRgb(float hue) {
float r = abs(hue * 6.0 - 3.0) - 1.0;
float g = 2.0 - abs(hue * 6.0 - 2.0);
float b = 2.0 - abs(hue * 6.0 - 4.0);
return clamp(vec3(r, g, b), 0.0, 1.0);
}
vec3 hslToRgb(vec3 hsl) {
vec3 rgb = hueToRgb(hsl.x);
float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
return (rgb - 0.5) * c + hsl.z;
}
void main() {
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
vec3 hslColor = rgbToHsl(inputColor.rgb);
hslColor.x = mod(hslColor.x + uHueAdjustmentDegrees, 1.0);
hslColor.y = clamp(hslColor.y + uSaturationAdjustment, 0.0, 1.0);
hslColor.z = clamp(hslColor.z + uLightnessAdjustment, 0.0, 1.0);
gl_FragColor = vec4(hslToRgb(hslColor), inputColor.a);
}
/*
* Copyright 2022 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 androidx.media3.effect;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.content.Context;
import androidx.media3.common.FrameProcessingException;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** Adjusts the HSL (Hue, Saturation, and Lightness) of a frame. */
public class HslAdjustment implements GlEffect {
/** A builder for {@code HslAdjustment} instances. */
public static final class Builder {
private float hueAdjustment;
private float saturationAdjustment;
private float lightnessAdjustment;
/** Creates a new instance with the default values. */
public Builder() {}
/**
* Rotates the hue of the frame by {@code hueAdjustmentDegrees}.
*
* <p>The Hue of the frame is defined in the interval of [0, 360] degrees. The actual degrees of
* hue adjustment applied is {@code hueAdjustmentDegrees % 360}.
*
* @param hueAdjustmentDegrees The hue adjustment in rotation degrees. The default value is
* {@code 0}, which means no change is applied.
*/
@CanIgnoreReturnValue
public Builder adjustHue(float hueAdjustmentDegrees) {
hueAdjustment = hueAdjustmentDegrees % 360;
return this;
}
/**
* Adjusts the saturation of the frame by {@code saturationAdjustment}.
*
* <p>Saturation is defined in the interval of [0, 100] where a saturation of {@code 0} will
* generate a grayscale frame and a saturation of {@code 100} has a maximum separation between
* the colors.
*
* @param saturationAdjustment The difference of how much the saturation will be adjusted in
* either direction. Needs to be in the interval of [-100, 100] and the default value is
* {@code 0}, which means no change is applied.
*/
@CanIgnoreReturnValue
public Builder adjustSaturation(float saturationAdjustment) {
checkArgument(
-100 <= saturationAdjustment && saturationAdjustment <= 100,
"Can adjust the saturation by only 100 in either direction, but provided "
+ saturationAdjustment);
this.saturationAdjustment = saturationAdjustment;
return this;
}
/**
* Adjusts the lightness of the frame by {@code lightnessAdjustment}.
*
* <p>Lightness is defined in the interval of [0, 100] where a lightness of {@code 0} is a black
* frame and a lightness of {@code 100} is a white frame.
*
* @param lightnessAdjustment The difference by how much the lightness will be adjusted in
* either direction. Needs to be in the interval of [-100, 100] and the default value is
* {@code 0}, which means no change is applied.
*/
@CanIgnoreReturnValue
public Builder adjustLightness(float lightnessAdjustment) {
checkArgument(
-100 <= lightnessAdjustment && lightnessAdjustment <= 100,
"Can adjust the lightness by only 100 in either direction, but provided "
+ lightnessAdjustment);
this.lightnessAdjustment = lightnessAdjustment;
return this;
}
/** Creates a new {@link HslAdjustment} instance. */
public HslAdjustment build() {
return new HslAdjustment(hueAdjustment, saturationAdjustment, lightnessAdjustment);
}
}
/** Indicates the hue adjustment in degrees. */
public final float hueAdjustmentDegrees;
/** Indicates the saturation adjustment. */
public final float saturationAdjustment;
/** Indicates the lightness adjustment. */
public final float lightnessAdjustment;
private HslAdjustment(
float hueAdjustmentDegrees, float saturationAdjustment, float lightnessAdjustment) {
this.hueAdjustmentDegrees = hueAdjustmentDegrees;
this.saturationAdjustment = saturationAdjustment;
this.lightnessAdjustment = lightnessAdjustment;
}
@Override
public HslProcessor toGlTextureProcessor(Context context, boolean useHdr)
throws FrameProcessingException {
return new HslProcessor(context, /* hslAdjustment= */ this, useHdr);
}
}
/*
* Copyright 2022 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 androidx.media3.effect;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Pair;
import androidx.media3.common.FrameProcessingException;
import com.google.android.exoplayer2.util.GlProgram;
import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
/** Applies the {@link HslAdjustment} to each frame in the fragment shader. */
/* package */ final class HslProcessor extends SingleFrameGlTextureProcessor {
private static final String VERTEX_SHADER_PATH = "shaders/vertex_shader_transformation_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "shaders/fragment_shader_hsl_es2.glsl";
private final GlProgram glProgram;
/**
* Creates a new instance.
*
* @param context The {@link Context}.
* @param hslAdjustment The {@link HslAdjustment} to apply to each frame in order.
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
* in linear RGB BT.2020. If {@code false}, colors will be in gamma RGB BT.709.
* @throws FrameProcessingException If a problem occurs while reading shader files.
*/
public HslProcessor(Context context, HslAdjustment hslAdjustment, boolean useHdr)
throws FrameProcessingException {
super(useHdr);
// TODO(b/241241680): Check if HDR <-> HSL works the same or not.
checkArgument(!useHdr, "HDR is not yet supported.");
try {
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
} catch (IOException | GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
float[] identityMatrix = new float[16];
Matrix.setIdentityM(identityMatrix, /* smOffset= */ 0);
glProgram.setFloatsUniform("uTransformationMatrix", identityMatrix);
glProgram.setFloatsUniform("uTexTransformationMatrix", identityMatrix);
// OpenGL operates in a [0, 1] unit range and thus we transform the HSL intervals into
// the unit interval as well. The hue is defined in the [0, 360] interval and saturation
// and lightness in the [0, 100] interval.
glProgram.setFloatUniform("uHueAdjustmentDegrees", hslAdjustment.hueAdjustmentDegrees / 360);
glProgram.setFloatUniform("uSaturationAdjustment", hslAdjustment.saturationAdjustment / 100);
glProgram.setFloatUniform("uLightnessAdjustment", hslAdjustment.lightnessAdjustment / 100);
}
@Override
public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
return Pair.create(inputWidth, inputHeight);
}
@Override
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
try {
glProgram.use();
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e, presentationTimeUs);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment