Преобразование значения цвета из числа с плавающей запятой 0..1 в байт 0..255 - PullRequest
18 голосов
/ 16 декабря 2009

Каков будет правильный способ преобразования значения цвета из числа с плавающей запятой в байт? Сначала я думал, что b=f*255.0 должен сделать это, но теперь я думаю, что в этом случае только точное 1.0 будет преобразовано в 255, но 0.9999 уже будет 254, что, вероятно, не что я хочу ...

Кажется, что b=f*256.0 было бы лучше, за исключением того, что он имел бы нежелательный случай 256 в случае точного 1.0.

В конце концов, я использую это:

#define F2B(f) ((f) >= 1.0 ? 255 : (int)((f)*256.0))

Ответы [ 7 ]

23 голосов
/ 16 декабря 2009

1.0 - единственный случай, который может пойти не так, поэтому обрабатывайте этот случай отдельно:

b = floor(f >= 1.0 ? 255 : f * 256.0)

Кроме того, возможно, стоит заставить f действительно 0

f2 = max(0.0, min(1.0, f))
b = floor(f2 == 1.0 ? 255 : f2 * 256.0)

Альтернативные безопасные решения:

b = (f >= 1.0 ? 255 : (f <= 0.0 ? 0 : (int)floor(f * 256.0)))

или

b = max(0, min(255, (int)floor(f * 256.0)))
8 голосов
/ 05 октября 2017

Я всегда делал round(f * 255.0).

Нет необходимости в тестировании (особый случай для 1) и / или в других ответах. Является ли это желательным ответом для ваших целей, зависит от того, насколько ваша цель состоит в том, чтобы максимально приблизить входные значения [моя формула] или разделить каждый компонент на 256 равных интервалов [другие формулы].

Возможным недостатком моей формулы является то, что интервалы 0 и 255 имеют только половину ширины других интервалов. За годы использования я еще не видел каких-либо визуальных доказательств того, что это плохо. Наоборот, я обнаружил, что предпочтительнее не попадать ни в одну из крайностей, пока вход не будет достаточно близок к этому - но это вопрос вкуса.

Возможный плюс в том, что [я считаю] относительные значения компонентов R-G-B (немного) более точны для более широкого диапазона входных значений.
Хотя я не пытался это доказать, это мое интуитивное чувство, учитывая, что для каждого компонента, который я округляю, получаем максимально близкое доступное целое число. (Например, я считаю, что если цвет имеет G ~ = 2 x R, эта формула будет чаще оставаться близкой к этому соотношению; хотя разница довольно мала, и есть много других цветов, с которыми формула 256 справляется лучше. Так что это может быть стирка.)

На практике подходы на основе 256 или 255, по-видимому, дают хорошие результаты.


Другой способ оценить 255 против 256, это изучить другое направление -
преобразование из байта 0..255 в число с плавающей запятой 0.0..1.0.

Формула, которая преобразует целочисленные значения 0..255 в равноотстоящие значения в диапазоне 0.0..1.0:

f = b / 255.0

В этом направлении не возникает вопроса, использовать ли 255 или 256: приведенная выше формула равна формуле, которая дает результаты с равным интервалом. Обратите внимание, что он использует 255.

Чтобы понять связь между формулами 255 в двух направлениях, рассмотрим эту диаграмму, если у вас было только 2 бита, поэтому значения целочисленные значения 0..3:

Диаграмма с использованием 3 для двух битов, аналогично 255 для 8 битов. Преобразование может быть сверху вниз или снизу вверх:

0 --|-- 1 --|-- 2 --|-- 3  
0 --|--1/3--|--2/3--|-- 0
   1/6     1/2     5/6

| - это границы между 4 диапазонами. Обратите внимание, что внутри значения с плавающей точкой и целочисленные значения находятся в середине их диапазонов. Обратите внимание, что интервал между всеми значениями является постоянным в обоих представлениях.

Если вы поймете эти диаграммы, вы поймете, почему я предпочитаю формулы на основе 255 перед формулами на основе 256.


Заявка : Если вы используете / 255.0 при переходе от байт для перемещения, но вы не используете round(f * 255.0) при переходе к байт float, , тогда увеличивается ошибка "среднего прохождения" . Подробности следуют.

Это легче всего измерить, начиная с числа с плавающей точкой, идущего до байта, затем обратно до числа с плавающей точкой. Для простого анализа используйте 2-битные диаграммы «0..3».

Начните с большого числа значений с плавающей запятой, равномерно распределенных от 0,0 до 1,0. В оба конца все эти значения будут сгруппированы по значениям 4.
Диаграмма имеет 6 диапазонов полуинтервалов:
0..1 / 6, 1 / 6..1 / 3, .., 5 / 6..1
Для каждого диапазона средняя ошибка приема-передачи составляет половину диапазона, поэтому 1/12 (минимальная ошибка равна нулю, максимальная ошибка равна 1/6, равномерно распределена).
Все диапазоны дают ту же ошибку; 1/12 - общая средняя ошибка при приеме в оба конца.

Если вместо этого вы используете какую-либо из формул * 256 или * 255.999, большинство результатов приема-передачи одинаковы, но некоторые перемещаются в соседний диапазон.
Любое изменение в другом диапазоне увеличивает ошибку ; например, если ошибка для одного входа с плавающей запятой ранее была немного меньше , чем 1/6, возвращение центра соседнего диапазона приводит к ошибке немного больше , чем 1/6. Например. 0,18 в оптимальной формуле => байт 1 => число с плавающей запятой 1/3 ~ = 0,333, для ошибки | 0.33-0.18| = 0.147; используя 256 формула => байт 0 => с плавающей запятой 0, для ошибки 0.18, которая является увеличением от оптимальной ошибки 0.147.

Диаграммы с использованием * 4 с / 3. Преобразование из одной строки в другую.
Обратите внимание на неровный интервал в первой строке: 0..3 / 8, 3 / 8..5 / 8, 5 / 8..1. Эти расстояния 3/8, 2/8, 3/8. Обратите внимание, что границы интервала последней строки отличаются от первой строки.

   0------|--3/8--|--5/8--|------0
         1/4     1/2     3/4
=> 0------|-- 1 --|-- 2 --|------3  

=> 0----|---1/3---|---2/3---|----0
       1/6       1/2       5/6

Единственный способ избежать этой увеличенной ошибки - использовать другую формулу при переходе от байта к числу с плавающей точкой. Если вы сильно верите в одну из формул 256, то я оставлю вам выбор оптимальной обратной формулы.
(В байтовом значении он должен возвращать среднюю точку значений с плавающей запятой, которые стали этим байтовым значением. За исключением от 0 до 0 и от 3 до 1. Или, возможно, от 0 до 1/8, от 3 до 7/8! На приведенной выше диаграмме это должен перенести вас из средней линии обратно в верхнюю.)

Но теперь у вас возникнет сложная для защиты ситуация, когда вы взяли одинаково расположенные байтовые значения и преобразовали их в неравно расположенные значения с плавающей точкой.

Это ваши параметры, если вы используете любое значение, отличное от 255, для целых чисел 0..255: либо увеличение средней ошибки приема-передачи, либо неравномерно распределенные значения в области с плавающей запятой .

6 голосов
/ 16 декабря 2009

Почему бы не попробовать что-то вроде

b=f*255.999

Избавляется от особого случая f==1, но 0,999 по-прежнему составляет 255

1 голос
/ 29 июля 2012

Принятое решение не удалось, когда оно сравнивает число с плавающей точкой как целочисленное.

Этот код работает нормально:

float f;
uint8_t i;
//byte to float
f =CLAMP(((float)((i &0x0000ff))) /255.0, 0.0, 1.0);
//float to byte
i =((uint8_t)(255.0f *CLAMP(f, 0.0, 1.0)));

если у вас нет CLAMP:

#define CLAMP(value, min, max) (((value) >(max)) ? (max) : (((value) <(min)) ? (min) : (value)))

Или для полного RGB:

integer_color =((uint8_t)(255.0f *CLAMP(float_color.r, 0.0, 1.0)) <<16) |
               ((uint8_t)(255.0f *CLAMP(float_color.g, 0.0, 1.0)) <<8) |
               ((uint8_t)(255.0f *CLAMP(float_color.b, 0.0, 1.0))) & 0xffffff;

float_color.r =CLAMP(((float)((integer_color &0xff0000) >>16)) /255.0, 0.0, 1.0);
float_color.g =CLAMP(((float)((integer_color &0x00ff00) >>8)) /255.0, 0.0, 1.0);
float_color.b =CLAMP(((float)((integer_color &0x0000ff))) /255.0, 0.0, 1.0);
0 голосов
/ 19 мая 2019

Что вы подразумеваете под правильным способом преобразования значения цвета из числа с плавающей запятой в байт? Вы имеете в виду, что если вы выберете единообразные случайные действительные числа из диапазона [0,1[, они будут однозначно распределены между 256 ячейками от 0 до 255?

Чтобы упростить задачу, мы предполагаем, что вместо значения float у нас есть действительное число, а вместо int мы хотим преобразовать в двухбитовое целое число, что-то вроде uint_2 - представление целого числа, которое состоит ровно из двух бит Это будет означать, что наши unit2_t могут иметь значения 00b, 01b, 10b и 11b (b означает, что у нас здесь есть двоичное число. Это также известно как соглашение Intel). Затем нам нужно придумать, какие интервалы действительных чисел следует сопоставлять каким целым значениям. Если вы хотите сопоставить [0,0.25[ с 0, [0.25,0.5[ с 1, [0.5,0.75[ с 2 и [0.75,1.0] с 3, преобразование можно выполнить с помощью b = std::floor(f * 4.0) (слово занимает только целая часть числа и игнорирует дробную часть). Это работает для всех номеров, кроме f=1. Простое изменение b = floor(f >= 1.0 ? 255 : f * 256.0) может решить эту проблему. Это уравнение гарантирует, что интервалы расположены на одинаковом расстоянии.

Если вы предполагаете, что наше действительное значение задано как число с плавающей точкой IEEE 754 одинарной точности, то в интервале [0,1] существует конечное число возможных представлений с плавающей запятой. Вы должны решить, какие представления этих вещественных чисел принадлежат какому целому представлению. Затем вы можете придумать некоторый исходный код, который преобразует ваше число с плавающей точкой в ​​целое число и проверить, соответствует ли оно вашему отображению. Может быть, int ig = int(255.99 * g); подходит вам или b = floor(f >= 1.0 ? 255 : f * 256.0). Это зависит от того, какое представление действительного числа вы хотите отобразить на какое представление целого числа.

Посмотрите на следующую программу. Это показывает, что разные конверсии делают разные вещи:

#include <iostream>

constexpr int realToIntegerPeterShirley(const double value) {
    return int(255.99 * value);
}

#define F2B(f) ((f) >= 1.0 ? 255 : (int)((f)*256.0))
constexpr int realToIntegerInkredibl(const double value) {
    return F2B(value);
}

const int realToIntegerMarkByers(const double value) {
    return std::floor(value >= 1.0 ? 255 : value * 256.0);
}

constexpr int realToIntegerToolmakerSteve(const double value) {
    return std::round(value * 255.0);
}

constexpr int realToIntegerErichKitzmueller(const double value) {
    return value*255.999;
}

constexpr int realToInteger(const float value) {
    return realToIntegerInkredibl(value);
}

int main() {
    {
        double value = 0.906285;
        std::cout << realToIntegerMarkByers(value) << std::endl; // output '232'
        std::cout << realToIntegerPeterShirley(value) << std::endl; // output '231'
    }

    {
        double value = 0.18345;
        std::cout << realToIntegerInkredibl(value) << std::endl; // output '46'
        std::cout << realToIntegerToolmakerSteve(value) << std::endl; // output '47'
    }

    {
        double value = 0.761719;
        std::cout << realToIntegerVertexwahn(value) << std::endl; // output '195'
        std::cout << realToIntegerErichKitzmueller(value) << std::endl; // output '194'
    }
}

Вы можете использовать этот маленький испытательный стенд для проведения экспериментов:

int main() {
    std::mt19937_64 rng;
    // initialize the random number generator with time-dependent seed
    uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
    std::seed_seq ss{uint32_t(timeSeed & 0xffffffff), uint32_t(timeSeed>>32)};
    rng.seed(ss);
    // initialize a uniform distribution between 0 and 1
    std::uniform_real_distribution<double> unif(0, 1);
    // ready to generate random numbers
    const int nSimulations = 1000000000;
    for (int i = 0; i < nSimulations; i++)
    {
        double currentRandomNumber = unif(rng);

        int firstProposal = realToIntegerMarkByers(currentRandomNumber);
        int secondProposal = realToIntegerErichKitzmueller(currentRandomNumber);

        if(firstProposal != secondProposal) {
            std::cout << "Different conversion with real " << currentRandomNumber << std::endl;
            return -1;
        }
    }
}

В конце я бы предложил не преобразовывать число с плавающей точкой в ​​целое. Сохраните изображение в виде данных с высоким динамическим диапазоном и выберите инструмент (например, http://djv.sourceforge.net/), который преобразует ваши данные в низкий динамический диапазон. Тональное отображение - это отдельная область исследований, и есть несколько инструментов, которые имеют приятный пользовательский интерфейс и предлагают вам все виды операторов тональной карты.

0 голосов
/ 11 сентября 2012
public static void floatToByte(float f)
{
     return (byte)(f * 255 % 256)
}

Значения <1 точно преобразуются. </p>

Значения, которые после преобразования попадают между 255 и 256, переводятся в 255 при преобразовании в байт.

Значения> 1 возвращаются в 0 с помощью оператора %.

0 голосов
/ 16 декабря 2009

Я считаю, что правильным является пол (f * 256), а не круглый. Это отобразит интервал 0..1 точно на 256 зон одинаковой длины.

[РЕДАКТИРОВАТЬ] и отметьте 256 как особый случай.

...