Skip to content

Commit

Permalink
Add support for gradient opacity stops (#2062)
Browse files Browse the repository at this point in the history
Fixes #2054
  • Loading branch information
gpeal committed Apr 23, 2022
1 parent b2f2161 commit 1ddca71
Show file tree
Hide file tree
Showing 2 changed files with 581 additions and 27 deletions.
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.");
}
}

0 comments on commit 1ddca71

Please sign in to comment.