-
Notifications
You must be signed in to change notification settings - Fork 6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add HSL Adjustments to the effects module.
PiperOrigin-RevId: 476144167
- Loading branch information
1 parent
afd829e
commit 2c27cd8
Showing
11 changed files
with
650 additions
and
0 deletions.
There are no files selected for viewing
360 changes: 360 additions & 0 deletions
360
.../libraries/effect/src/androidTest/java/androidx/media3/effect/HslAdjustmentPixelTest.java
Large diffs are not rendered by default.
Oops, something went wrong.
77 changes: 77 additions & 0 deletions
77
.../android_libs/media/libraries/effect/src/main/assets/shaders/fragment_shader_hsl_es2.glsl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#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); | ||
} |
118 changes: 118 additions & 0 deletions
118
...droid_libs/media/libraries/effect/src/main/java/androidx/media3/effect/HslAdjustment.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* 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); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
...ndroid_libs/media/libraries/effect/src/main/java/androidx/media3/effect/HslProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* 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); | ||
} | ||
} | ||
} |
Binary file added
BIN
+544 KB
...s/media/bitmap/sample_mp4_first_frame/linear_colors/adjust_all_hsl_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+148 KB
...assets/media/bitmap/sample_mp4_first_frame/linear_colors/decrease_lightness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+511 KB
...assets/media/bitmap/sample_mp4_first_frame/linear_colors/increase_lightness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+560 KB
...assets/media/bitmap/sample_mp4_first_frame/linear_colors/maximum_saturation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+317 KB
...assets/media/bitmap/sample_mp4_first_frame/linear_colors/minimum_saturation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+535 KB
.../media/bitmap/sample_mp4_first_frame/linear_colors/rotate_hue_by_60_degrees.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+552 KB
...tmap/sample_mp4_first_frame/linear_colors/rotate_hue_by_negative_90_degrees.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.