Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for gradient opacity stops #2062

Merged
merged 4 commits into from Apr 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
114 changes: 87 additions & 27 deletions lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
Expand Up @@ -2,14 +2,13 @@

import android.graphics.Color;

import androidx.annotation.IntRange;

import com.airbnb.lottie.model.content.GradientColor;
import com.airbnb.lottie.parser.moshi.JsonReader;
import com.airbnb.lottie.utils.MiscUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GradientColorParser implements com.airbnb.lottie.parser.ValueParser<GradientColor> {
Expand Down Expand Up @@ -105,7 +104,7 @@ public GradientColor parse(JsonReader reader, float scale)
}

GradientColor gradientColor = new GradientColor(positions, colors);
addOpacityStopsToGradientIfNeeded(gradientColor, array);
gradientColor = addOpacityStopsToGradientIfNeeded(gradientColor, array);
return gradientColor;
}

Expand All @@ -118,47 +117,108 @@ public GradientColor parse(JsonReader reader, float scale)
* This should be a good approximation is nearly all cases. However, if there are many more
* opacity stops than color stops, information will be lost.
*/
private void addOpacityStopsToGradientIfNeeded(GradientColor gradientColor, List<Float> array) {
private GradientColor addOpacityStopsToGradientIfNeeded(GradientColor gradientColor, List<Float> array) {
int startIndex = colorPoints * 4;
if (array.size() <= startIndex) {
return;
return gradientColor;
}

float[] colorStopPositions = gradientColor.getPositions();
int[] colorStopColors = gradientColor.getColors();

int opacityStops = (array.size() - startIndex) / 2;
double[] positions = new double[opacityStops];
double[] opacities = new double[opacityStops];
float[] opacityStopPositions = new float[opacityStops];
float[] opacityStopOpacities = new float[opacityStops];

for (int i = startIndex, j = 0; i < array.size(); i++) {
if (i % 2 == 0) {
positions[j] = array.get(i);
opacityStopPositions[j] = array.get(i);
} else {
opacities[j] = array.get(i);
opacityStopOpacities[j] = array.get(i);
j++;
}
}

for (int i = 0; i < gradientColor.getSize(); i++) {
int color = gradientColor.getColors()[i];
color = Color.argb(
getOpacityAtPosition(gradientColor.getPositions()[i], positions, opacities),
Color.red(color),
Color.green(color),
Color.blue(color)
);
gradientColor.getColors()[i] = color;
int newColorPoints = colorPoints + opacityStops;
float[] newPositions = new float[newColorPoints];
int[] newColors = new int[newColorPoints];

System.arraycopy(gradientColor.getPositions(), 0, newPositions, 0, colorPoints);
System.arraycopy(opacityStopPositions, 0, newPositions, colorPoints, opacityStops);
Arrays.sort(newPositions);

for (int i = 0; i < newColorPoints; i++) {
float position = newPositions[i];
int colorStopIndex = Arrays.binarySearch(colorStopPositions, position);
int opacityIndex = Arrays.binarySearch(opacityStopPositions, position);
if (colorStopIndex < 0 || opacityIndex > 0) {
// This is a stop derived from an opacity stop.
if (opacityIndex < 0) {
throw new IllegalArgumentException("Unable to find opacity stop position for " + position);
// TODO: use this backup instead…
// opacityIndex = -(opacityIndex + 1);
}
newColors[i] = getColorInBetweenColorStops(position, opacityStopOpacities[opacityIndex], colorStopPositions, colorStopColors);
} else {
// This os a step derived from a color stop.
newColors[i] = getColorWithOpacityStops(colorStopColors[colorStopIndex], position, opacityStopPositions, opacityStopOpacities);
}
}
return new GradientColor(newPositions, newColors);
}

private int getColorInBetweenColorStops(float position, float opacity, float[] colorStopPositions, int[] colorStopColors) {
if (colorStopColors.length < 2 || position == colorStopPositions[0]) {
return colorStopColors[0];
}
for (int i = 1; i < colorStopPositions.length; i++) {
float colorStopPosition = colorStopPositions[i];
if (colorStopPosition < position && i != colorStopPositions.length - 1) {
continue;
}
// We found the position in which position is between i - 1 and i.
float distanceBetweenColors = colorStopPositions[i] - colorStopPositions[i - 1];
float distanceToLowerColor = position - colorStopPositions[i - 1];
float percentage = distanceToLowerColor / distanceBetweenColors;
int upperColor = colorStopColors[i];
int lowerColor = colorStopColors[i - 1];
int a = (int) (opacity * 255);
int r = MiscUtils.lerp(Color.red(lowerColor), Color.red(upperColor), percentage);
int g = MiscUtils.lerp(Color.green(lowerColor), Color.green(upperColor), percentage);
int b = MiscUtils.lerp(Color.blue(lowerColor), Color.blue(upperColor), percentage);
return Color.argb(a, r, g, b);
}
throw new IllegalArgumentException("Unreachable code.");
}

@IntRange(from = 0, to = 255)
private int getOpacityAtPosition(double position, double[] positions, double[] opacities) {
for (int i = 1; i < positions.length; i++) {
double lastPosition = positions[i - 1];
double thisPosition = positions[i];
if (positions[i] >= position) {
double progress = MiscUtils.clamp((position - lastPosition) / (thisPosition - lastPosition), 0, 1);
return (int) (255 * MiscUtils.lerp(opacities[i - 1], opacities[i], progress));
private int getColorWithOpacityStops(int color, float position, float[] opacityStopPositions, float[] opacityStopOpacities) {
if (opacityStopOpacities.length < 2 || position <= opacityStopPositions[0]) {
int a = (int) (opacityStopOpacities[0] * 255);
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
return Color.argb(a, r, g, b);
}
for (int i = 1; i < opacityStopPositions.length; i++) {
float opacityStopPosition = opacityStopPositions[i];
if (opacityStopPosition < position && i != opacityStopPositions.length - 1) {
continue;
}
final int a;
if (opacityStopPosition <= position) {
a = (int) (opacityStopOpacities[i] * 255);
} else {
// We found the position in which position in between i - 1 and i.
float distanceBetweenOpacities = opacityStopPositions[i] - opacityStopPositions[i - 1];
float distanceToLowerOpacity = position - opacityStopPositions[i - 1];
float percentage = distanceToLowerOpacity / distanceBetweenOpacities;
a = (int) (MiscUtils.lerp(opacityStopOpacities[i - 1], opacityStopOpacities[i], percentage) * 255);
}
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
return Color.argb(a, r, g, b);
}
return (int) (255 * opacities[opacities.length - 1]);
throw new IllegalArgumentException("Unreachable code.");
}
}