HDR-фотографии: что находится внутри JPEG с gain map
На HDR-дисплее солнце или блик на фотографии иногда выглядит ярче белого фона страницы. Разберём без магии, откуда экран получает команду сделать отдельные пиксели ярче. Пример - файл IMG_20260816_183557.jpg с Xiaomi 17.

HDR-эффект на этой фотографии виден только на HDR-экране в совместимом браузере.
Пиксель, канал и биты
Здесь канал означает компонент цифрового представления цвета. В модели RGB цвет пикселя задают три числа: красное R, зелёное G и синее B.
У 8-битного канала есть 2⁸ = 256 возможных кодов, от 0 до 255. Например, после декодирования программа может представить один пиксель так:
один пиксель в декодированном RGB-буфере
R: 11010110 = 214
G: 10011100 = 156 всего 8 + 8 + 8 = 24 бита
B: 00110101 = 53 без учёта прозрачности
После декодирования пиксели могут лежать в памяти как RGB, BGR или RGBA, но это не описывает устройство JPEG-файла, а даже BMP добавляет заголовки и собственные правила хранения.
RGB не является единственной схемой кодирования:
| Схема | Компоненты | Где встречается |
|---|---|---|
| Grayscale | одно значение серого | маски, карты, чёрно-белые изображения |
| RGB / RGBA | красный, зелёный, синий и необязательная прозрачность | декодированные изображения, графика, интерфейсы |
| YCbCr | яркостная Y и две цветоразностные Cb/Cr | JPEG и видео, часто с меньшим разрешением цветовых компонентов |
Для однозначного цвета нужны также цветовое пространство и передаточная функция. Чёрный в обычном RGB-примере кодируется как (0, 0, 0), белый как (255, 255, 255), но это коды цвета, а не значения яркости в нитах.
Больше битов дают больше промежуточных кодов на канал:
| Разрядность | Значений канала | Вариантов RGB-пикселя |
|---|---|---|
| 8 бит | 256 | 16,7 млн |
| 10 бит | 1024 | 1,07 млрд |
| 12 бит | 4096 | 68,7 млрд |
10 или 12 бит уменьшают ступеньки на градиентах и дают запас для обработки. Но именно поэтому 8-битный JPEG всё ещё может давать HDR: разрядность описывает число кодов внутри базового изображения, а яркость выше SDR-белого добавляют второе изображение с gain map и метаданные для его применения.
Как JPEG лежит в файле и в памяти
После декодирования программа действительно может держать изображение как массив RGB-пикселей. Для кадра 4096 × 3072 при 8 битах на три канала такой несжатый массив занимал бы:
4096 × 3072 × 3 байта = 37 748 736 байт ≈ 36 МиБ
JPEG на диске устроен иначе. При типичном кодировании RGB сначала преобразуется в яркостную составляющую Y и две цветоразностные Cb/Cr. Цветовые каналы часто уменьшаются в разрешении, изображение делится на блоки, значения переводятся в частотные коэффициенты дискретным косинусным преобразованием, округляются и кодируются без лишних повторов.
flowchart TB
subgraph memory[В памяти редактора: около 36 МиБ]
RGB[Пиксели RGB<br/>4096 × 3072 × 3 байта]
end
subgraph encoder[Сохранение JPEG]
direction LR
YCC[Преобразование<br/>RGB → YCbCr]
SUB[Уменьшение<br/>цветовых каналов]
DCT[Блоки и DCT]
Q[Округление<br/>коэффициентов]
end
FILE[(Сжатый JPEG<br/>около 4,77 МБ)]
DEC[Декодирование]
SCREEN[Пиксели<br/>на экране]
RGB --> YCC --> SUB --> DCT --> Q --> FILE
FILE --> DEC --> SCREEN
Главная потеря качества происходит при округлении коэффициентов. Поэтому JPEG компактный, но повторное пересохранение может добавить артефакты. В исследуемом файле основной кадр занимает около 4,77 МБ вместо примерно 36 МиБ декодированного RGB-массива.
Число 8 в заголовке JPEG означает точность отсчётов базового изображения. Оно не запрещает приложить к файлу второе изображение и метаданные, которые вместе описывают HDR.
Из каких частей состоит этот файл
JPEG - последовательность секций, или сегментов. Диапазон маркеров APP0...APP15 задан в приложении B рекомендации ITU-T T.81, совместном тексте с ISO/IEC 10918-1, а правила применения APPn описаны в действующей рекомендации ITU-T T.86. Маркеры APP1, APP2 и другие позволяют помещать рядом со сжатыми пикселями служебные данные. Разбор файла Xiaomi дал такую карту сегментов в порядке следования. Для APPn в таблице указано значение двухбайтового поля длины JPEG-сегмента: оно включает само поле, но не включает двухбайтовый маркер FFEn.
| Порядок | JPEG | Смещение | Сегмент | Длина / параметры |
|---|---|---|---|---|
| 1 | основной | 2 | APP1 XMP |
948 Б |
| 2 | основной | 952 | APP3 ALGO_COMMON |
22 672 Б |
| 3 | основной | 23 626 | APP2 ICC_PROFILE |
648 Б |
| 4 | основной | 24 276 | APP4 XIAOMI_CUSTOMIZE |
546 Б |
| 5 | основной | 24 824 | APP3 OISINFO |
7 574 Б |
| 6 | основной | 32 400 | APP2 urn:iso:std:iso:ts:21496:-1 |
34 Б |
| 7 | основной | 32 436 | APP2 MPF |
88 Б |
| 8 | основной | 32 660 | SOF0 |
4096 × 3072, 3 компонента, 8 бит |
| 9 | основной | 4 773 110 | EOI |
конец базового JPEG |
| 10 | gain map | 4 773 114 | APP1 XMP |
549 Б |
| 11 | gain map | 4 773 665 | APP2 ISO 21496-1 |
91 Б |
| 12 | gain map | 4 773 758 | APP0 JFIF |
16 Б |
| 13 | gain map | 4 773 845 | SOF0 |
2048 × 1536, 1 компонент, 8 бит |
Старый декодер читает первый JPEG до маркера конца изображения и показывает нормальную SDR-фотографию. Совместимый декодер находит второе изображение, читает параметры gain map и строит HDR-представление. Поэтому расширение .jpg не противоречит HDR.
EXIF: чем и как снято
EXIF описан в официальной спецификации CIPA DC-008-Translation-2026, Exif Version 3.1. Это действительно актуальная редакция на дату статьи: CIPA опубликовала её 30 января 2026 года как пересмотр CIPA DC-008-2024. EXIF представляет собой стандартную таблицу фотографических метаданных: модель камеры, выдержка, диафрагма, ISO-чувствительность, ориентация, время и иногда GPS. Эти поля описывают происхождение снимка, но не содержат сами видимые пиксели.
У Xiaomi часть сведений находится в фирменном блоке камеры: модель Xiaomi 17 и Hdr: auto. Здесь Hdr: auto говорит, что HDR-режим камеры был автоматическим. Этого недостаточно, чтобы доказать наличие воспроизводимого HDR: производитель мог свести несколько экспозиций в обычный SDR JPEG. Доказательство находится в gain map и её метаданных.
ISO в EXIF и ISO 21496-1 - разные значения слова ISO. В EXIF ISO 100, 400 или 1600 означает настройку светочувствительности при съёмке. ISO 21496-1 - номер международного технического стандарта хранения gain map.
XMP: расширяемые инструкции
XMP - текстовые метаданные в формате, похожем на XML. EXIF имеет заранее определённый набор фотографических полей, а XMP допускает пространства имён производителей и программ. Поэтому Adobe может добавить поля hdrgm:*, не меняя сам JPEG-декодер.
Во втором изображении этого файла записано:
hdrgm:Version="1.0"
hdrgm:GainMapMin="0"
hdrgm:GainMapMax="2.32193"
hdrgm:Gamma="1"
hdrgm:OffsetSDR="0"
hdrgm:OffsetHDR="0"
hdrgm:HDRCapacityMin="0"
hdrgm:HDRCapacityMax="2.32193"
hdrgm:BaseRenditionIsHDR="False"
Метаданные сами по себе почти не занимают места. Основной дополнительный объём создаёт JPEG с картой усиления.
MPF: где искать вторую картинку
В Ultra HDR основным механизмом обнаружения gain map служит не MPF, а XMP-каталог Google Photos Container. Он лежит в APP1 XMP базового кадра. Container:Directory перечисляет основной элемент с Item:Semantic="Primary" и дополнительный с Item:Semantic="GainMap"; Item:Length задаёт длину добавленного изображения. В исследуемом файле каталог выглядит так:
<Container:Directory>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<Container:Item
Item:Semantic="Primary"
Item:Mime="image/jpeg"/>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<Container:Item
Item:Semantic="GainMap"
Item:Mime="image/jpeg"
Item:Length="931559"/>
</rdf:li>
</rdf:Seq>
</Container:Directory>
MPF, Multi-Picture Format, идёт дополнением: его индекс в APP2 хранит размеры, смещения и типы нескольких JPEG-изображений. Это похоже на второе оглавление того же контейнера. Спецификация Ultra HDR требует после записи вторичного изображения добавить и MPF, и GContainer XMP. Ни один из них сам по себе не описывает математику яркости.
Рядом присутствует метка urn:iso:std:iso:ts:21496:-1 и бинарные данные ISO 21496-1. Стандарт определяет gain map как способ преобразования между двумя представлениями динамического диапазона. В этом файле есть Adobe XMP-поля и ISO-вариант метаданных; GContainer и MPF указывают расположение изображений.
Rendition: представление одной фотографии
Rendition здесь означает не новый сюжет и не копию другого размера, а одно визуальное представление той же сцены. Например:
- SDR rendition рассчитано на обычный диапазон дисплея;
- HDR rendition сохраняет более яркие света;
- gain map содержит разницу, необходимую для перехода между ними.
BaseRenditionIsHDR="False" означает, что основной JPEG является SDR-представлением. Если программа игнорирует всё после него, фотография всё равно выглядит корректно.
Что кодирует gain map
Gain map, или карта усиления, похожа на чёрно-белую маску. Каждая её точка сообщает не готовую яркость, а множитель для соответствующего участка базового изображения:

Базовый кадр: цветное SDR-представление, которое видят программы без поддержки gain map.

Gain map: светлые области требуют большего усиления, вплоть до ×5; тёмные остаются без усиления, то есть ×1.
flowchart LR
SDR[SDR-база<br/>цвет и мелкие детали]
MAP[Gain map<br/>тёмное: около ×1<br/>светлое: до ×5]
META[Метаданные<br/>min · max · gamma<br/>capacity · offsets]
HEAD[Запас дисплея<br/>HDR white / SDR white]
DEC[HDR-декодер<br/>растянуть карту<br/>вычислить weight<br/>усилить линейный RGB]
OUT[Представление для экрана<br/>SDR · промежуточное · полное HDR]
SDR --> DEC
MAP --> DEC
META --> DEC
HEAD --> DEC
DEC --> OUT
Карта может быть меньше основного кадра, потому что усиление меняется плавнее, чем мелкие детали фотографии. В нашем файле каждая сторона карты ровно вдвое меньше стороны базы. При показе декодер растягивает и плавно интерполирует её. Одного серого канала достаточно, когда R, G и B усиливаются одинаково. Формат также допускает отдельные значения для цветовых каналов.
Упрощённая математика для SDR-базы выглядит так:
map = значение пикселя карты от 0 до 1
logGain = lerp(GainMapMin, GainMapMax, map ^ (1 / Gamma))
boost = log2(HDR-белый дисплея / SDR-белый дисплея)
weight = clamp((boost - HDRCapacityMin) /
(HDRCapacityMax - HDRCapacityMin), 0, 1)
HDR = (SDR + OffsetSDR) × 2 ^ (logGain × weight) - OffsetHDR
lerp(a, b, t) выбирает положение между минимумом и максимумом, а clamp ограничивает результат диапазоном 0...1. Если Gamma равна 1, как в этом файле, степень не меняет карту. Реальная операция выполняется над линейными цветовыми значениями, а не прямо над числами 0...255 из JPEG.
Например, возьмём линейное значение SDR-компонента 0,4, значение карты 0,5 и дисплей, у которого HDR-белый в 4 раза ярче SDR-белого. Для параметров этого файла расчёт проходит насквозь так:
logGain = lerp(0, 2,32193, 0,5) = 1,160965
boost = log2(4) = 2
weight = 2 / 2,32193 ≈ 0,86135
HDR = 0,4 × 2 ^ (1,160965 × 0,86135)
= 0,4 × 2¹ = 0,8
То есть этот участок получает двукратное усиление: не максимальные ×5 из метаданных, потому что значение карты находится посередине, а запас дисплея ограничен четырёхкратным отношением белого.
Есть и цена в байтах. Совместимая JPEG-база заканчивается на смещении 4 773 112, а весь файл занимает 5 704 671 байт. Значит, второй JPEG с gain map и его служебными данными добавляет 931 559 байт: 931 559 / 4 773 112 ≈ 19,5% к размеру обычной базы.
Поля означают следующее:
| Поле | Смысл в декодировании | Значение в файле |
|---|---|---|
GainMapMin |
минимальный логарифм усиления | 0, то есть 2⁰ = ×1 |
GainMapMax |
максимальный логарифм усиления | 2,32193, то есть 2²·³²¹⁹³ ≈ ×5 |
Gamma |
показатель кодирования карты, который декодер обращает степенью 1 / Gamma |
1, без дополнительного изгиба |
OffsetSDR/HDR |
малые смещения до и после умножения, полезные около чёрного | 0 и 0 |
HDRCapacityMin/Max |
при каком запасе дисплея начинать и заканчивать применение карты | от 0 до 2,32193 ступени |
BaseRenditionIsHDR |
является ли базовый JPEG HDR-стороной преобразования | False, база SDR |
Числа заданы в ступенях экспозиции, то есть как логарифмы по основанию 2. Поэтому GainMapMax="2.32193" кодирует верхнюю границу примерно ×5, а не 2,32-кратное усиление. Это не обещает, что любой пиксель станет в пять раз ярче: значение зависит от самой карты, а weight ограничивает эффект возможностями экрана.
Почему Gamma - не та же gamma экрана
Слово gamma используется в двух местах:
hdrgm:Gammaизгибает значения внутри gain map, чтобы точнее распределить её ограниченные коды;- передаточная функция изображения, например приближённая gamma sRGB, связывает код цвета с линейным светом.
Это разные этапы. В данном файле ImageMagick сообщает gamma базового изображения около 0.4545, а поле gain map равно 1. Первое относится к интерпретации цвета, второе - к декодированию карты усиления. Gain map также не равна обычной gamma-коррекции: gamma применяет одну кривую ко всем пикселям с одинаковым входным значением, а карта может задать разное усиление двум участкам одинаковой исходной яркости.
Как изображение становится ярче белого
Обычный белый интерфейса служит точкой отсчёта SDR. HDR-дисплей имеет headroom, или запас яркости выше этой точки. Декодер узнаёт доступный запас, вычисляет weight, восстанавливает HDR-пиксели по карте и передаёт их оконной системе. Поэтому блик может физически излучать больше света, хотя базовый JPEG всё ещё ограничен значением 255.
Нужны одновременно три условия:
- файл содержит базу, gain map и корректные метаданные;
- приложение умеет их декодировать;
- дисплей и режим системы предоставляют HDR-запас.
На SDR-мониторе или в старой программе показывается только базовое rendition. При промежуточном запасе яркости карта применяется частично, а не просто включается или выключается.
Почему не просто 10-битный AVIF, HEIC или JPEG XL
Эти форматы тоже умеют хранить HDR: спецификация AVIF поддерживает HDR и разрядности AV1, Apple описывает 10-битный HEIF для ISO HDR, а JPEG Committee указывает для JPEG XL HDR и высокую разрядность. Но 10-битный HDR-кадр сам по себе не даёт старому JPEG-декодеру готовую SDR-версию и не кодирует две художественно согласованные версии для разного запаса яркости. Ultra HDR решает именно эту задачу: обычный 8-битный JPEG остаётся безопасной SDR-базой, а gain map позволяет плавно получить промежуточный или полный HDR.
Это не противопоставление JPEG и HEIF. Apple Adaptive HDR использует ту же стандартизованную в ISO 21496-1 модель SDR + gain map и определяет её хранение как в HEIF, так и в JPEG. Начиная с iOS 18, iPhone 15 и iPhone 15 Pro перешли на Adaptive HDR; в HEIC gain map хранится как дополнительное изображение HEIF, а не как второй JPEG. Поэтому выбор контейнера и выбор gain map являются разными решениями.
Где gain map действительно работает
Поддержка состоит из двух уровней: программа должна сохранить и прочитать gain map, а система с дисплеем должны дать ей HDR-запас. На дату статьи подтверждены следующие сценарии:
- Браузеры. В Chromium функция
GainmapHdrImagesвключена по умолчанию, поэтому Ultra HDR JPEG воспроизводится в Chrome на системе с доступным HDR-запасом. В Safari 26.0 WebKit добавил HDR-изображения на веб-страницах, а в мае 2026 года отдельно добавил извлечение ISO Gain Map и Apple HDR Gain Map через системный декодер. На SDR-дисплее и в декодере без gain map показывается базовый JPEG. - Галереи. Android умеет декодировать и отображать Ultra HDR начиная с Android 14. Google Photos прямо заявляет поддержку Ultra HDR: отдельная фотография открывается в HDR на совместимом экране, а миниатюры в общей сетке остаются SDR. На платформах Apple системные API читают и показывают gain-map HDR; начиная с iOS 18, iPhone 15 и 15 Pro создают описанный выше Adaptive HDR.
- Социальные сети. Для Instagram есть опубликованное подтверждение от команды Android: приложение позволяет редактировать до десяти Ultra HDR-фотографий и публиковать их с сохранением полного динамического диапазона. Для остальных соцсетей без документации нельзя считать сохранение gain map гарантированным.
Что срезает HDR, тоже определяется не расширением файла, а трактом обработки. Google Photos предупреждает, что неподдерживаемая правка сохраняет результат как SDR, а highlight video всегда использует SDR-версии. Обычный декодер, который прочитал базовый JPEG и затем заново закодировал пиксели, не переносит автоматически GContainer XMP, MPF, второй JPEG и ISO-метаданные. Напротив, стандартные операции Android вроде кадрирования и поворота могут сохранить Ultra HDR, если выполняются gain-map-aware API, поэтому нельзя утверждать, что любая обработка обязательно уничтожает карту.
Инструменты
В таблицу вошли только возможности, подтверждённые документацией самих проектов.
| Инструмент | Что умеет | Отдаёт ли JPEG с gain map |
|---|---|---|
| Extra Bright Images | Локально в браузере строит HDR из обычного изображения; настройки Make brights brighter, Gain и Highlight threshold действуют автоматически по всему кадру |
Да - кнопка Download Ultra HDR отдаёт Ultra HDR JPEG; отдельно доступен Native HDR PNG |
| Lightroom | Импортирует, редактирует и экспортирует HDR; при сохранении JPEG с HDR-правками сохраняет SDR-изображение и HDR-детали в gain map | Да - JPEG с HDR-правками сохраняется с gain map |
| Adobe Camera Raw | Открывает и редактирует HDR, поддерживает локальные маски и сохраняет HDR в JPEG; отдельная справка по Gain Map описывает представление base + gain map + metadata |
Да - JPEG указан среди поддерживаемых HDR-форматов сохранения |
libultrahdr CLI |
Эталонный кодек Ultra HDR: кодирует из HDR/SDR-представлений, собирает готовые SDR JPEG и gain map, декодирует и выводит метаданные в probe-режиме | Да - ultrahdr_app по умолчанию создаёт out.jpeg с базой, gain map и метаданными |
exiftool |
Читает XMP и MPF, показывает параметры вложенных изображений и позволяет извлечь дополнительный JPEG; документация помечает MPF-теги как недоступные для записи | Нет - это инструмент анализа метаданных, не энкодер JPEG с gain map |
Если исходник уже потерял детали в пересвеченной области, ни один инструмент не восстановит их одним усилением: изменится яркость только имеющихся данных.
Как не потерять HDR при публикации
Обычный обработчик изображений может прочитать только первый JPEG, уменьшить его и сохранить новый файл. Тогда MPF, XMP, ISO-метаданные и сама gain map исчезнут. Наличие .jpg и даже копирование EXIF не гарантируют сохранение HDR.
Проверить метаданные и извлечь карту можно воспроизводимыми командами из корня проекта:
# Показать Adobe HDR gain map, MPF и Google Photos Container.
exiftool -G1 -s -XMP-hdrgm:All -MPF:All -XMP-GContainer:All \
content/files/IMG_20260816_183557.jpg
# Извлечь второе изображение из MPF без декодирования и повторного JPEG-сжатия.
exiftool -b -MPImage2 content/files/IMG_20260816_183557.jpg \
> content/files/IMG_20260816_183557-gainmap.jpg
Если установленный ExifTool не отдаёт MPImage2, короткий Python-фолбэк находит второй маркер начала JPEG FF D8 и сохраняет данные до конца файла:
from pathlib import Path
src = Path("content/files/IMG_20260816_183557.jpg")
dst = Path("content/files/IMG_20260816_183557-gainmap.jpg")
data = src.read_bytes()
second_soi = data.find(b"\xff\xd8", 2)
if second_soi < 0 or not data.endswith(b"\xff\xd9"):
raise SystemExit("second JPEG not found")
dst.write_bytes(data[second_soi:])
print(f"wrote {dst}: {len(data) - second_soi} bytes from offset {second_soi}")
Этот фолбэк подходит для данного файла, где второй JPEG занимает весь хвост. Для произвольного контейнера надёжнее использовать MPF или GContainer, потому что после дополнительного изображения могут находиться другие данные.
Практическая проверка после обработки:
1. Есть ли внутри второй JPEG?
2. Остались ли MPF и метка ISO 21496-1 или XMP hdrgm?
3. Совпадает ли SDR-вид в старом просмотрщике?
4. Появляются ли дополнительные света в HDR-просмотрщике?
Именно поэтому снимок в этой статье подключён как исходный статический JPEG, а не как автоматически созданная WebP-миниатюра.
Короткий итог
Исследуемый файл содержит не «8-битный HDR-пиксель». Он содержит совместимый 8-битный SDR JPEG, второй одноканальный JPEG с пространственной картой усиления и инструкции по их объединению. EXIF рассказывает о съёмке, XMP и ISO 21496-1 описывают преобразование, GContainer XMP объявляет основной кадр и gain map, MPF дополняет его индексом изображений, а HDR-декодер собирает из них rendition под возможности конкретного дисплея.
Источники для дальнейшего чтения: