Ошибка преобразования HSV в RGB и наоборот

118
20 ноября 2020, 17:10

Народ у меня есть slider в проекте который двигает другого но вроде не должен.

Это часть кода который работает когда нажимаю на slider -

public void ChangeValue()
{
    float h, s, v;
    Color.RGBToHSV(GetNewColor(), out h, out s, out v);
    Color color = Color.HSVToRGB(h, s, ValueSlider.value);
    SetColor(color);
}

В нём есть функция SetColor которая получает Color32 и меняет позицию кнопки в цветном круге -

public void SetColor(Color32 color32)
{
    float hue, saturation, value;
    float transparence;
    float rotation, distance;
    Color.RGBToHSV(color32, out hue, out saturation, out value);
    transparence = color32.a;
    rotation = hue * 360 * Mathf.Deg2Rad;
    distance = saturation * maxDistance;
    Knob.transform.position = new Vector2(Center.x + distance * Mathf.Sin(rotation), Center.y + distance * Mathf.Cos(rotation));
    TransparenceSlider.value = transparence / 255;
    NewColor = color32;
    NewColorImage.color = color32;
}

Как вы видите сверху переменная Value вообще не влияет на Knob.transform.position он только для того чтобы можно было использовать функцию Color.RGBToHSV.

И ещё это может понадобиться -

Center = ColorsRawImage.transform.position;
maxDistance = ColorsRawImage.GetComponent<RectTransform>().rect.width / 2;

Мне надо любым образом решить проблему с Slider-ом.
P.S. Если что-то ещё будет нужно скажите я добавлю.

Вот полный скрипт если нужно -

public RawImage ColorsRawImage;
Texture2D ColorsTexture;
public Image Knob;
public Image SelectedColorImage, NewColorImage;
public Slider ValueSlider, TransparenceSlider;
float maxDistance;
bool onCanvas;
Vector2 Center;
Color32 SelectedColor, NewColor;
[Header("Main")]
public Image OpenButton;
/* ==================================== Start =====================================*/
private void Start()
{
    /* ==================================== Colors Texture =====================================*/
    ColorsTexture = new Texture2D(399, 399)
    {
        filterMode = FilterMode.Point
    };
    ColorsRawImage.texture = ColorsTexture;
    maxDistance = ColorsRawImage.GetComponent<RectTransform>().rect.width / 2;
    Vector2Int CenterInt = new Vector2Int(ColorsTexture.width / 2 + 1, ColorsTexture.height / 2 + 1);
    Center = ColorsRawImage.transform.position;
    for (int y = 0; y < ColorsTexture.height; y++)
    {
        for (int x = 0; x < ColorsTexture.width; x++)
        {
            ColorsTexture.SetPixel(x, y, Color.white);
        }
    }
    int maxRadius = ColorsTexture.width / 2;
    for (float radius = 0; radius <= maxRadius; radius++)
    {
        for (float angle = 0; angle <= 360; angle += 0.1f)
        {
            int x = (int)(CenterInt.x + radius * Mathf.Sin(angle * Mathf.Deg2Rad));
            int y = (int)(CenterInt.y + radius * Mathf.Cos(angle * Mathf.Deg2Rad));
            Color color = Color.HSVToRGB(angle / 360, radius / maxRadius, 1);
            ColorsTexture.SetPixel(x, y, color);
        }
    }
    ColorsTexture.Apply();
    /* ==================================== Initializing Color =====================================*/
    SetColor(new Color32(255, 170, 85, 255));
    RefreshColor();
}
/* ==================================== Update =====================================*/
private void Update()
{
    float distance = 0, rotation = 0;
    if (Input.GetMouseButtonDown(0))
    {
        Vector2 PressedPosition = Input.mousePosition;
        Vector2 Center = ColorsRawImage.transform.position;
        Vector2 PressedVector = new Vector2(PressedPosition.x - Center.x, PressedPosition.y - Center.y);
        distance = Mathf.Sqrt
        (
            Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2)
        );
        if (distance <= maxDistance)
        {
            onCanvas = true;
        }
        else
        {
            onCanvas = false;
        }
    }
    if (Input.GetMouseButton(0) && onCanvas)
    {
        Vector2 VerticalVector = new Vector2(0, maxDistance);
        Vector2 PressedPosition = Input.mousePosition;
        Vector2 PressedVector = new Vector2(PressedPosition.x - Center.x, PressedPosition.y - Center.y);
        /* ==================================== Get Position =====================================*/
        distance = Mathf.Sqrt
        (
            Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2)
        );
        if (distance != 0)
        {
            rotation = Mathf.Acos
            (
                (VerticalVector.x * PressedVector.x + VerticalVector.y * PressedVector.y) /
                Mathf.Sqrt
                (
                    (Mathf.Pow(VerticalVector.x, 2) + Mathf.Pow(VerticalVector.y, 2)) *
                    (Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2))
                )
            );
        }
        if (PressedPosition.x < Center.x)
        {
            rotation = 2 * Mathf.PI - rotation;
        }
        /* ==================================== Set Color =====================================*/
        Color color;
        if (distance <= maxDistance)
        {
            color = Color.HSVToRGB(rotation * Mathf.Rad2Deg / 360, distance / maxDistance, 255);
        }
        else
        {
            color = Color.HSVToRGB(rotation * Mathf.Rad2Deg / 360, 1, 255);
        }
        Color32 color32 = new Color32((byte)color.r, (byte)color.g, (byte)color.b, (byte)(TransparenceSlider.value * 255));
        SetColor(color32);
    }
}
/* ==================================== Functions =====================================*/
public void ChangeValue()
{
    float hue, saturation, value;
    Color.RGBToHSV(GetNewColor(), out hue, out saturation, out value);
    Color color = Color.HSVToRGB(hue, saturation, ValueSlider.value);
    SetColor(new Color32((byte)(color.r * 255), (byte)(color.g * 255), (byte)(color.b * 255), NewColor.a));
}
public void ChangeTransparence()
{
    float transparence = TransparenceSlider.value;
    SetColor(new Color32(GetNewColor().r, GetNewColor().g, GetNewColor().b, (byte)(transparence * 255)));
}
/* ==================================== Global Functions =====================================*/
public void SetColor(Color32 color32)
{
    float hue, saturation, value;
    float transparence;
    float rotation, distance;
    Color.RGBToHSV(color32, out hue, out saturation, out value);
    transparence = color32.a;
    rotation = hue * 360 * Mathf.Deg2Rad;
    distance = saturation * maxDistance;
    Knob.transform.position = new Vector2(Center.x + distance * Mathf.Sin(rotation), Center.y + distance * Mathf.Cos(rotation));
    //ValueSlider.value = value;
    TransparenceSlider.value = transparence / 255;
    NewColor = color32;
    NewColorImage.color = color32;
}
public void RefreshColor()
{
    SelectedColor = NewColor;
    SelectedColorImage.color = NewColor;
    OpenButton.color = NewColor;
}
public Color32 GetSelectedColor()
{
    return SelectedColor;
}
public Color32 GetNewColor()
{
    return NewColor;
}
Answer 1

2 проблемы:

  1. При установки Value цвет ставится в центральную точку - черный/белый цвет, из какого бы цвета Value не устанавливался
  2. Смещение HSV цвета в сторону ближайшей RGB компоненты

Проблема #1

Что такое HSV цвет с Value == 0? Это черный цвет, RGB(0, 0, 0), когда мы поднимем Value обратно в 1, то совершенно закономерно получим белый цвет и вот почему:

Сделаем цепочку преобразований от, допустим, бирюзового цвета в черный и обратно через Value:

  1. Уменьшение Value до нуля: HSV(124, 184, 255) -> HSV(124, 184, 0)
  2. Преобразование HSV -> RGB: HSV(124, 184, 0) -> RGB(0, 0, 0)
  3. Обратное преобразование RGB -> HSV: RGB(0, 0, 0) -> HSV(0, 0, 0)
  4. Поднятие Value в единицу: HSV(0, 0, 0) -> HSV(0, 0, 255)

После всех этих махинаций видно, что HSV значения до и после преобразования отличаются: (124, 184, 0) -> (0, 0, 0). Цветовое представление RGB и HSV никак не связаны, они лишь имеют преобразования друг в друга.

Данный пример является самым показательным:

У HSV цвета может быть 255^2 черных одинаковых цветов: (0-255, 0-255, 0), когда у RGB это ровно одна комбинация: (0, 0, 0). Из-за этого преобразования и теряется Hue-Saturation информация.

Как решить эту проблему?

Раз уж нужно использовать HSV колесо и получать с него RGB цвета - храните для этого цветового колеса HSV значение цвета и реализуйте метод, возвращающий RGB значение через преобразование HSV цвета.

Проблема #2

Ну тут вообще детская ошибка - неправильное округление:

SetColor(
    new Color32(
        (byte)(color.r * 255), 
        (byte)(color.g * 255), 
        (byte)(color.b * 255), 
        NewColor.a)
    );

Выводим эти числа до и после преобразования в байт и видим такую картину:

Данный код всегда округляет вниз - операция floor, что и приводит к ошибкам преобразования в HSV.

Как решить эту проблему?

Перестать использовать миллиард преобразований из HSV в RGB и обратно, тем более если речь идет про Color32, где значения цветовых каналов - это целые значения из диапазона [0..255], а HSV и Color - это дробные значения [0..1]. Зачем нужно все туда-сюда дергать, если проще просто хранить все в HSV и при необходимости преобразовывать в RGB хоть Color, хоть Color32?

Исправленный под ошибки выше код, непонятные мне методы я просто убрал - сами допишете, что вам там нужно:

public class HSV_Circle : MonoBehaviour
{
    struct HSV {
        public float hue;
        public float saturation;
        public float value;
        public float transparence;
    }
    public RawImage ColorsRawImage;
    Texture2D ColorsTexture;
    public Image Knob;
    public Image SelectedColorImage, NewColorImage;
    public Slider ValueSlider, TransparenceSlider;
    float maxDistance;
    bool onCanvas;
    Vector2 Center;
    HSV hsv_color;
    //Color32 SelectedColor, NewColor;
    [Header("Main")]
    public Image OpenButton;
    /* ==================================== Start =====================================*/
    private void Start() {
        /* ==================================== Colors Texture =====================================*/
        ColorsTexture = new Texture2D(399, 399) {
            filterMode = FilterMode.Point
        };
        ColorsRawImage.texture = ColorsTexture;
        maxDistance = ColorsRawImage.GetComponent<RectTransform>().rect.width / 2;
        Vector2Int CenterInt = new Vector2Int(ColorsTexture.width / 2 + 1, ColorsTexture.height / 2 + 1);
        Center = ColorsRawImage.transform.position;
        for (int y = 0; y < ColorsTexture.height; y++) {
            for (int x = 0; x < ColorsTexture.width; x++) {
                ColorsTexture.SetPixel(x, y, Color.white);
            }
        }
        int maxRadius = ColorsTexture.width / 2;
        for (float radius = 0; radius <= maxRadius; radius++) {
            for (float angle = 0; angle <= 360; angle += 0.1f) {
                int x = (int)(CenterInt.x + radius * Mathf.Sin(angle * Mathf.Deg2Rad));
                int y = (int)(CenterInt.y + radius * Mathf.Cos(angle * Mathf.Deg2Rad));
                Color color = Color.HSVToRGB(angle / 360, radius / maxRadius, 1);
                ColorsTexture.SetPixel(x, y, color);
            }
        }
        ColorsTexture.Apply();
        /* ==================================== Initializing Color =====================================*/
        hsv_color = new HSV();
        hsv_color.value = 1;
        SetColor();
    }
    /* ==================================== Update =====================================*/
    private void Update() {
        float distance = 0, rotation = 0;
        if (Input.GetMouseButtonDown(0)) {
            Vector2 PressedPosition = Input.mousePosition;
            Vector2 Center = ColorsRawImage.transform.position;
            Vector2 PressedVector = new Vector2(PressedPosition.x - Center.x, PressedPosition.y - Center.y);
            distance = Mathf.Sqrt
            (
                Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2)
            );
            if (distance <= maxDistance) {
                onCanvas = true;
            }
            else {
                onCanvas = false;
            }
        }
        if (Input.GetMouseButton(0) && onCanvas) {
            Vector2 VerticalVector = new Vector2(0, maxDistance);
            Vector2 PressedPosition = Input.mousePosition;
            Vector2 PressedVector = new Vector2(PressedPosition.x - Center.x, PressedPosition.y - Center.y);
            /* ==================================== Get Position =====================================*/
            distance = Mathf.Sqrt
            (
                Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2)
            );
            if (distance != 0) {
                rotation = Mathf.Acos
                (
                    (VerticalVector.x * PressedVector.x + VerticalVector.y * PressedVector.y) /
                    Mathf.Sqrt
                    (
                        (Mathf.Pow(VerticalVector.x, 2) + Mathf.Pow(VerticalVector.y, 2)) *
                        (Mathf.Pow(PressedVector.x, 2) + Mathf.Pow(PressedVector.y, 2))
                    )
                );
            }
            if (PressedPosition.x < Center.x) {
                rotation = 2 * Mathf.PI - rotation;
            }
            /* ==================================== Set Color =====================================*/
            hsv_color.hue = rotation * Mathf.Rad2Deg / 360;
            hsv_color.value = ValueSlider.value;
            hsv_color.transparence = TransparenceSlider.value;
            if (distance <= maxDistance) {
                hsv_color.saturation = distance / maxDistance;
            }
            else {
                hsv_color.saturation = 1;
            }
            SetColor();
        }
    }
    /* ==================================== Functions =====================================*/
    public void ChangeValue() {
        hsv_color.value = ValueSlider.value;
        SetColor();
    }
    public void ChangeTransparence() {
        hsv_color.transparence = TransparenceSlider.value;
        SetColor();
    }
    /* ==================================== Global Functions =====================================*/
    public void SetColor() {
        float rotation = hsv_color.hue * 360 * Mathf.Deg2Rad;
        float distance = hsv_color.saturation * maxDistance;
        Knob.transform.position = new Vector2(Center.x + distance * Mathf.Sin(rotation), Center.y + distance * Mathf.Cos(rotation));
        NewColorImage.color = GetSelectedColor();
    }
    public Color32 GetSelectedColor() {
        return Color.HSVToRGB(hsv_color.hue, hsv_color.saturation, hsv_color.value);
    }
}
READ ALSO
Ограничить вводимые данные. SQL

Ограничить вводимые данные. SQL

Пытался уже решить этот вопрос, но никак не получаеться решить проблему

118
Yii 2 проверка на авторизированного пользователя его id

Yii 2 проверка на авторизированного пользователя его id

Нужен совет правильно я сделал проверку в layoutsphp

120
как решить проблему с saveHTML()?

как решить проблему с saveHTML()?

Использую saveHTML() для сохранения данных в файл, но данная функция перестраивает структуру кодаБыло так:

133
Проблема при разворачивании битрикса

Проблема при разворачивании битрикса

Сайт работал в кодировке UTF-8Конфигурация сервера не соответствует требованиям

188