- Поддерживаемые версии
- 1.14
- 1.15
- 1.16
- 1.17
- 1.18
- 1.19
(Я не являюсь автором этой статьи, а лишь представляю её вольный перевод. Оригинал: https://www.spigotmc.org/threads/gradient-chat-particles.470496/)
После того как Minecraft представил поддержку всех видов цветов, стали возможны «Цветовые градиенты» что-то вроде этого:
Тут то и возникла некоторая путаница в том, как этого добиться, какие есть возможные методы и как достичь разных вещей, таких как градиенты, содержащие более двух цветов, различия между HSV / RGB и способы интерполирования между разными цветами. Я хочу рассмотреть все это в этой теме, конечно же вместе с примерами кода.
1. Интерполяция
Интерполяция - это процесс соединения нескольких точек «плавной» кривой. В примерах 1 и 2 у нас есть только 2 точки (красный в начале и зелёный в конце). В других примерах у нас есть три точки, которые необходимо соединить. Важное замечание - в наших примерах "точки" - это цвета.
Начнём с интерполяции всего двух точек. Мы всегда будем определять эти две точки следующим образом:
Первая точка находится в (x, y) = (0, from), а вторая находится в точке (E-1, to). from и to - это два значения цвета, которые мы хотим соединить, N - длина строки или количество элементов, которые нужно раскрасить.
На рисунке выше используются два метода: линейная интерполяция и квадратичная интерполяция.
Наша цель - найти функцию f(e), проходящую через две точки. Если мы используем линейную интерполяцию, то получим уравнение:
f(e) = from + ((to - from) / (E - 1)) * e.
Если мы вставим 0, мы получим f(0) = from. Если мы вставим E-1, мы получим from + ((to - from) / (E-1)) * (E-1) = from + to - from = to.
Если мы используем квадратичную интерполяцию, уравнения станут несколько сложнее, поскольку у нас открывается гораздо больше свободы.
Можно сделать вывод о том, что вы можете использовать любую функцию, если она «плавная» и соответствует двум пунктам. Различные функции влияют на то, как цвет фактически «выцветает».
В коде я определю (функциональный) интерфейс, который позволит сгруппировать различные алгоритмы интерполяции. Я также предоставлю два метода: один для линейной интерполяции двух точек, а другой - для квадратичной:
Переходим к нашим градиентам.
2. Цветовое пространство
Цвета могут быть представлены в разных цветовых пространствах. Самый распространенный (вероятно) - это RGB. Цвет может состоять из красного, зеленого и синего компонентов. Смешивание этих трех цветов вместе в конечном итоге даст другой цвет. Все эти компоненты (красный, зеленый и синий) могут иметь любое значение от 0 до 255.
Первый градиент, который я покажу, представляет собой линейную интерполяцию каждого из трех компонентов RGB (Пример 1). Для этого нам просто нужно применить линейную интерполяцию к каждому из этих компонентов.
Теперь это позволяет нам плавно интерполировать между двумя цветами. Однако это может показаться немного неудовлетворительным. При градиенте цвет становится темнее, а затем снова ярче. Однако что, если мы хотим оставаться на одной яркости? Для этого нам понадобится другая цветовая схема.
HSV разделяет цвет на оттенок, насыщенность и яркость. Если мы изменим значение оттенка, мы останемся на одном значении яркости. Для преобразования из RGB в HSV и наоборот мы можем использовать java.awt.Color:
3. Многоцветные градиенты.
Наконец, я хочу посмотреть, что происходит, когда мы хотим использовать несколько цветов. В примерах 3, 4 и 5 мы переходим от красного к зеленому, а затем от зеленого к красному. В примерах 6-10 мы выполняем градиент от красного к зеленому, а затем обратно к красному, при этом каждому цвету присваивается разный интервал с различными методами интерполяции при использовании RGB, а также HSV.
Для этого мы можем просто повторно использовать предоставленные методы и интерполировать две последующие точки:
Массив частей позволяет нам контролировать, где должны быть размещены разные цвета. Особый случай «ноль» означает, что все цвета должны быть равномерно распределены. Примеры 3, 4 и 5 являются результатом этого метода. Во всех случаях цвета распределены равномерно. Пример 3 использует линейную интерполяцию, 4 и 5 квадратичную интерполяцию с двумя разными режимами, описанными выше.
Примеры 6, 7 и 8 являются примерами для цветов = {красный, зеленый, зеленый, красный} (что означает переход от красного к зеленому, от зеленого к зеленому и, наконец, от зеленого к красному), в то время как массив частей - это части = { 0,1, 0,8, 0,1}, что означает переход от красного к зеленому на 10% всего пространства, 80% пространства не должно изменяться, а оставаться зеленым и, наконец, переходить от зеленого к красному. В примере 6 используется линейная интерполяция, в примерах 7 и 8 используется квадратичная интерполяция.
Для оптимизации квадратичной интерполяции в примерах 9 и 10 показана версия, которая использует разные режимы для запуска и остановки и работает в цветовом пространстве HSV. Он несколько жестко запрограммирован, но показывает различные возможные варианты:
После того как Minecraft представил поддержку всех видов цветов, стали возможны «Цветовые градиенты» что-то вроде этого:
1. Интерполяция
Интерполяция - это процесс соединения нескольких точек «плавной» кривой. В примерах 1 и 2 у нас есть только 2 точки (красный в начале и зелёный в конце). В других примерах у нас есть три точки, которые необходимо соединить. Важное замечание - в наших примерах "точки" - это цвета.
Начнём с интерполяции всего двух точек. Мы всегда будем определять эти две точки следующим образом:
Первая точка находится в (x, y) = (0, from), а вторая находится в точке (E-1, to). from и to - это два значения цвета, которые мы хотим соединить, N - длина строки или количество элементов, которые нужно раскрасить.
На рисунке выше используются два метода: линейная интерполяция и квадратичная интерполяция.
Наша цель - найти функцию f(e), проходящую через две точки. Если мы используем линейную интерполяцию, то получим уравнение:
f(e) = from + ((to - from) / (E - 1)) * e.
Если мы вставим 0, мы получим f(0) = from. Если мы вставим E-1, мы получим from + ((to - from) / (E-1)) * (E-1) = from + to - from = to.
Вот некоторые результаты с использованием линейной и квадратичной интерполяции.
Если мы используем квадратичную интерполяцию, уравнения станут несколько сложнее, поскольку у нас открывается гораздо больше свободы.
Можно сделать вывод о том, что вы можете использовать любую функцию, если она «плавная» и соответствует двум пунктам. Различные функции влияют на то, как цвет фактически «выцветает».
В коде я определю (функциональный) интерфейс, который позволит сгруппировать различные алгоритмы интерполяции. Я также предоставлю два метода: один для линейной интерполяции двух точек, а другой - для квадратичной:
Java:
@FunctionalInterface
interface Interpolator {
double[] interpolate(double from, double to, int max);
}
Java:
private double[] linear(double from, double to, int max) {
final double[] res = new double[max];
for (int i = 0; i < max; i++) {
res[i] = from + i * ((to - from) / (max - 1));
}
return res;
}
Java:
// mode == true: начинается "медленно" и становится "быстрее", см. оранжевую кривую
// mode == false: начинается "быстро" и становится "медленнее", см. желтую кривую
private double[] quadratic(double from, double to, int max, boolean mode) {
final double[] results = new double[max];
if (mode) {
double a = (to - from) / (max * max);
for (int i = 0; i < results.length; i++) {
results[i] = a * i * i + from;
}
} else {
double a = (from - to) / (max * max);
double b = - 2 * a * max;
for (int i = 0; i < results.length; i++) {
results[i] = a * i * i + b * i + from;
}
}
return results;
}
2. Цветовое пространство
Цвета могут быть представлены в разных цветовых пространствах. Самый распространенный (вероятно) - это RGB. Цвет может состоять из красного, зеленого и синего компонентов. Смешивание этих трех цветов вместе в конечном итоге даст другой цвет. Все эти компоненты (красный, зеленый и синий) могут иметь любое значение от 0 до 255.
Первый градиент, который я покажу, представляет собой линейную интерполяцию каждого из трех компонентов RGB (Пример 1). Для этого нам просто нужно применить линейную интерполяцию к каждому из этих компонентов.
Java:
private String rgbGradient(String str, Color from, Color to, Interpolator interpolator) {
// интерполируем каждый компонент отдельно
final double[] red = interpolator.interpolate(from.getRed(), to.getRed(), str.length());
final double[] green = interpolator.interpolate(from.getGreen(), to.getGreen(), str.length());
final double[] blue = interpolator.interpolate(from.getBlue(), to.getBlue(), str.length());
final StringBuilder builder = new StringBuilder();
// создаем строку, которая соответствует входной строке, но
// к каждому символу применяется другой цвет
for (int i = 0; i < str.length(); i++) {
builder.append(ChatColor.of(new Color(
(int) Math.round(red[i]),
(int) Math.round(green[i]),
(int) Math.round(blue[i]))))
.append(str.charAt(i));
}
}
HSV разделяет цвет на оттенок, насыщенность и яркость. Если мы изменим значение оттенка, мы останемся на одном значении яркости. Для преобразования из RGB в HSV и наоборот мы можем использовать java.awt.Color:
Java:
private String hsvGradient(String str, Color from, Color to, Interpolator interpolator) {
// возвращает массив с плавающей запятой, где hsv[0] = оттенок, hsv[1] = насыщенность, hsv[2] = значение / яркость
final float[] hsvFrom = Color.RGBtoHSB(from.getRed(), from.getGreen(), from.getBlue(), null);
final float[] hsvTo = Color.RGBtoHSB(to.getRed(), to.getGreen(), to.getBlue(), null);
final double[] h = interpolator.interpolate(hsvFrom[0], hsvTo[0], str.length());
final double[] s = interpolator.interpolate(hsvFrom[1], hsvTo[1], str.length());
final double[] v = interpolator.interpolate(hsvFrom[2], hsvTo[2], str.length());
final StringBuilder builder = new StringBuilder();
for (int i = 0 ; i < str.length(); i++) {
builder.append(ChatColor.of(Color.getHSBColor((float) h[i], (float) s[i], (float) v[i]))).append(str.charAt(i));
}
return builder.toString();
}
3. Многоцветные градиенты.
Наконец, я хочу посмотреть, что происходит, когда мы хотим использовать несколько цветов. В примерах 3, 4 и 5 мы переходим от красного к зеленому, а затем от зеленого к красному. В примерах 6-10 мы выполняем градиент от красного к зеленому, а затем обратно к красному, при этом каждому цвету присваивается разный интервал с различными методами интерполяции при использовании RGB, а также HSV.
Для этого мы можем просто повторно использовать предоставленные методы и интерполировать две последующие точки:
Java:
private String multiRgbGradient(String str, Color[] colors, @Nullable double[] portions, Interpolator interpolator) {
final double[] p;
if (portions == null) {
p = new double[colors.length - 1];
Arrays.fill(p, 1 / (double) p.length);
} else {
p = portions;
}
Preconditions.checkArgument(colors.length >= 2);
Preconditions.checkArgument(p.length == colors.length - 1);
final StringBuilder builder = new StringBuilder();
int strIndex = 0;
for (int i = 0; i < colors.length - 1; i++) {
builder.append(rgbGradient(
str.substring(strIndex, strIndex + (int) (p[i] * str.length())),
colors[i],
colors[i + 1],
interpolator));
strIndex += p[i] * str.length();
}
return builder.toString();
}
Примеры 6, 7 и 8 являются примерами для цветов = {красный, зеленый, зеленый, красный} (что означает переход от красного к зеленому, от зеленого к зеленому и, наконец, от зеленого к красному), в то время как массив частей - это части = { 0,1, 0,8, 0,1}, что означает переход от красного к зеленому на 10% всего пространства, 80% пространства не должно изменяться, а оставаться зеленым и, наконец, переходить от зеленого к красному. В примере 6 используется линейная интерполяция, в примерах 7 и 8 используется квадратичная интерполяция.
Для оптимизации квадратичной интерполяции в примерах 9 и 10 показана версия, которая использует разные режимы для запуска и остановки и работает в цветовом пространстве HSV. Он несколько жестко запрограммирован, но показывает различные возможные варианты:
Java:
private String multiHsvQuadraticGradient(String str, boolean first) {
final StringBuilder builder = new StringBuilder();
builder.append(hsvGradient(
str.substring(0, (int) (0.2 * str.length())),
Color.RED,
Color.GREEN,
(from, to, max) -> this.quadratic(from, to, max, first)
));
for (int i = (int) (0.2 * str.length()); i < (int) (0.8 * str.length()); i++) {
builder.append(ChatColor.of(Color.GREEN)).append(str.charAt(i));
}
builder.append(hsvGradient(
str.substring((int) (0.8 * str.length())),
Color.GREEN,
Color.RED,
(from, to, max) -> this.quadratic(from, to, max, !first)
));
return builder.toString();
}