Допустим, вы работаете с цветами RGB: каждый цвет представлен с тремя значениями интенсивности или яркости. Вы должны выбирать между «линейным RGB» и «sRGB». На данный момент мы упростим ситуацию, игнорируя три различных интенсивности, и предположим, что у вас есть только одна интенсивность: то есть вы имеете дело только с оттенками серого.
В линейном цветовом пространстве отношение между сохраняемыми вами числами и интенсивностями, которые они представляют, является линейным. На практике это означает, что если вы удвоите число, вы удвоите интенсивность (яркость серого). Если вы хотите сложить две интенсивности вместе (потому что вы вычисляете интенсивность на основе вкладов двух источников света или потому что вы добавляете прозрачный объект поверх непрозрачного объекта), вы можете сделать это, просто добавив два числа вместе. Если вы выполняете какое-либо двухмерное смешивание или трехмерное затенение, или почти любую обработку изображений, тогда вам нужны ваши интенсивности в линейном цветовом пространстве., поэтому вы можете просто складывать, вычитать, умножать и делить числа, чтобы иметь такой же эффект на интенсивности. Большинство алгоритмов обработки цвета и рендеринга дают правильные результаты только с линейным RGB, если вы не добавляете дополнительные веса ко всему.
Звучит очень просто, но есть проблема. Чувствительность человеческого глаза к свету меньше при низкой интенсивности, чем при высокой. То есть, если вы составите список всех интенсивностей, которые вы можете различить, темных будет больше, чем светлых. Другими словами, вы можете отличить темные оттенки серого лучше, чем светлые оттенки серого. В частности, если вы используете 8 бит для представления вашей интенсивности и делаете это в линейном цветовом пространстве, вы получите слишком много светлых оттенков и недостаточно темных оттенков. Вы получаете полосы в темных областях, в то время как в светлых областях вы тратите кусочки на разные оттенки почти белого, которые пользователь не может различить.
Чтобы избежать этой проблемы и максимально использовать эти 8 бит, мы обычно используем sRGB . Стандарт sRGB сообщает вам кривую, которую нужно использовать, чтобы сделать ваши цвета нелинейными. Кривая более мелкая внизу, поэтому у вас может быть больше темных серых оттенков, и круче вверху, поэтому у вас будет меньше светлых серых оттенков. Если вы удвоите число, вы более чем удвоите интенсивность. Это означает, что если вы сложите цвета sRGB вместе, вы получите более светлый результат, чем должен быть. В наши дни большинство мониторов интерпретируют свои входные цвета как sRGB. Итак, когда вы помещаете цвет на экран или сохраняете его в текстуре с 8 битами на канал, сохраняйте его как sRGB , чтобы вы могли наилучшим образом использовать эти 8 бит.
Вы заметите, что теперь у нас есть проблема: мы хотим, чтобы наши цвета обрабатывались в линейном пространстве, но сохранялись в sRGB. Это означает, что вы в конечном итоге выполняете преобразование из sRGB в линейное при чтении и преобразование из линейного в sRGB при записи. Как мы уже говорили, линейной 8-битной интенсивности недостаточно темных оттенков, это вызовет проблемы, поэтому есть еще одно практическое правило: не используйте 8-битные линейные цвета, если вы можете этого избежать. Становится обычным следовать правилу, согласно которому 8-битные цвета всегда являются sRGB, поэтому вы выполняете преобразование sRGB в линейное одновременно с расширением интенсивности с 8 до 16 бит или с целого числа на число с плавающей запятой; аналогично, когда вы закончите обработку с плавающей запятой, вы сужаете до 8 бит одновременно с преобразованием в sRGB. Если вы будете следовать этим правилам,
Когда вы читаете изображение sRGB и вам нужна линейная интенсивность, примените эту формулу к каждой интенсивности:
float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);
Иначе говоря, если вы хотите записать изображение как sRGB, примените эту формулу к каждой линейной интенсивности:
float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )
В обоих случаях значение s с плавающей запятой находится в диапазоне от 0 до 1, поэтому, если вы читаете 8-битные целые числа, вы хотите сначала разделить на 255, а если вы пишете 8-битные целые числа, вы хотите умножить на 255 в последнюю очередь, как обычно. Это все, что вам нужно знать для работы с sRGB.
До сих пор я имел дело только с одной интенсивностью, но есть более умные вещи, связанные с цветами. Человеческий глаз может различать разные яркости лучше, чем разные оттенки (с технической точки зрения, он имеет лучшее разрешение по яркости, чем цветность), поэтому вы можете еще лучше использовать свои 24 бита, сохраняя яркость отдельно от оттенка. Это то, что пытаются делать представления YUV, YCrCb и т.д. Канал Y представляет собой общую яркость цвета и использует больше битов (или имеет более высокое пространственное разрешение), чем два других канала. Таким образом, вам (всегда) не нужно применять кривую, как в случае с интенсивностями RGB. YUV - это линейное цветовое пространство, поэтому, если вы удвоите число в канале Y, вы удвоите яркость цвета, но вы не можете складывать или умножать цвета YUV вместе, как вы можете с цветами RGB, так что это '
Думаю, это ответ на ваш вопрос, поэтому я закончу короткой исторической заметкой. До sRGB старые ЭЛТ имели встроенную нелинейность. Если вы удвоите напряжение для пикселя, вы увеличите интенсивность более чем вдвое. Насколько же больше отличалось для каждого монитора, и этот параметр получил название гамма . Такое поведение было полезно, потому что оно означало, что вы могли получить больше темных, чем светлых, но также означало, что вы не могли определить, насколько яркими будут ваши цвета на ЭЛТ пользователя, если вы не откалибруете его сначала. Гамма-коррекцияозначает преобразование цветов, с которых вы начинаете (возможно, линейные), и преобразование их для гаммы ЭЛТ пользователя. OpenGL пришел из этой эпохи, поэтому его поведение sRGB иногда немного сбивает с толку. Но производители графических процессоров теперь склонны работать с соглашением, которое я описал выше: когда вы сохраняете 8-битную интенсивность в текстуре или фреймбуфере, это sRGB, а когда вы обрабатываете цвета, это линейно. Например, в OpenGL ES 3.0 каждый буфер кадра и текстура имеют «флаг sRGB», который можно включить, чтобы включить автоматическое преобразование при чтении и записи. Вам вообще не нужно явно выполнять преобразование sRGB или гамма-коррекцию.