mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(media-proxy): tone map HDR video instead of refusing it (#2606)
This commit is contained in:
@@ -9,8 +9,9 @@ fn is_distribution_lib_dir(path: &Path) -> bool {
|
||||
path.starts_with("/usr/lib") || path.starts_with("/lib")
|
||||
}
|
||||
|
||||
const NATIVE_SHIM_SOURCES: [&str; 16] = [
|
||||
const NATIVE_SHIM_SOURCES: [&str; 17] = [
|
||||
"src/webp_animation.c",
|
||||
"src/native_shim/hdr_color.c",
|
||||
"src/native_shim/av_input.c",
|
||||
"src/native_shim/av_frame_rgba.c",
|
||||
"src/native_shim/vips_image.c",
|
||||
@@ -28,7 +29,8 @@ const NATIVE_SHIM_SOURCES: [&str; 16] = [
|
||||
"src/native_shim/nsfw_webp.c",
|
||||
];
|
||||
|
||||
const NATIVE_SHIM_HEADERS: [&str; 3] = [
|
||||
const NATIVE_SHIM_HEADERS: [&str; 4] = [
|
||||
"src/native_shim/hdr_color.h",
|
||||
"src/vips_shim.h",
|
||||
"src/webp_animation.h",
|
||||
"src/native_shim/native_shim_internal.h",
|
||||
|
||||
@@ -463,7 +463,8 @@ fn nsfw_frame_emit_failure_is_reported_instead_of_decoder_eof() {
|
||||
assert_eq!(NativeStatus::Ok, sdr_status);
|
||||
assert_eq!(3, sdr_frames.expect("sdr frames").len());
|
||||
|
||||
let rejected = matroska_video(&["-colorspace", "bt2020nc"]).expect("ffmpeg tags the matrix");
|
||||
let rejected =
|
||||
matroska_video(&["-vf", "setparams=color_trc=smpte428"]).expect("ffmpeg tags the transfer");
|
||||
let (status, frames) = av_nsfw_extract(&rejected, ×tamps);
|
||||
assert_eq!(NativeStatus::Unsupported, status);
|
||||
assert_eq!(Err(NSFWFrameCopyError::InvalidOutput), frames);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use super::super::{MediaError, extract_video_thumbnail};
|
||||
use super::super::{
|
||||
MediaError, VideoThumbnailOptions, extract_video_thumbnail,
|
||||
extract_video_thumbnail_with_options,
|
||||
};
|
||||
use super::fixtures::{decode_rgba, metadata_value, test_media_limits};
|
||||
use crate::{
|
||||
image_quality::ResolvedImageQuality,
|
||||
output_format::OutputFormat,
|
||||
test_fixtures::{
|
||||
ffmpeg_gen_media, ffmpeg_gen_mp4, ffmpeg_gen_rotated_mp4, ffmpeg_mirror_mp4, png_dimensions,
|
||||
@@ -115,32 +119,171 @@ fn video_thumbnail_accepts_every_widened_sdr_transfer() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_thumbnail_still_rejects_pq_bt2020_hdr() {
|
||||
fn video_thumbnail_tone_maps_hdr_instead_of_refusing_it() {
|
||||
let Some(hdr) = color_tagged_video("bt2020", "smpte2084", "bt2020nc") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
let thumb = extract_video_thumbnail(&hdr, OutputFormat::PNG, &test_media_limits())
|
||||
.expect("pq bt2020 video should thumbnail");
|
||||
assert_eq!(png_dimensions(&thumb.bytes), Some((320, 240)));
|
||||
|
||||
for (primaries, transfer, matrix) in [
|
||||
("bt709", "smpte2084", "bt709"),
|
||||
("bt2020", "arib-std-b67", "bt709"),
|
||||
("bt2020", "arib-std-b67", "bt2020nc"),
|
||||
("bt2020", "bt709", "bt709"),
|
||||
("smpte432", "bt709", "bt709"),
|
||||
] {
|
||||
let video = color_tagged_video(primaries, transfer, matrix)
|
||||
.unwrap_or_else(|| panic!("{primaries}/{transfer}/{matrix} fixture"));
|
||||
let thumb = extract_video_thumbnail(&video, OutputFormat::PNG, &test_media_limits())
|
||||
.unwrap_or_else(|error| {
|
||||
panic!("{primaries}/{transfer}/{matrix} should thumbnail: {error:?}")
|
||||
});
|
||||
assert_eq!(
|
||||
png_dimensions(&thumb.bytes),
|
||||
Some((320, 240)),
|
||||
"{primaries}/{transfer}/{matrix}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hdr_video_thumbnail_still_errors_on_an_expired_deadline() {
|
||||
let Some(hdr) = color_tagged_video("bt2020", "smpte2084", "bt2020nc") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
assert!(matches!(
|
||||
extract_video_thumbnail_with_options(
|
||||
&hdr,
|
||||
VideoThumbnailOptions {
|
||||
format: OutputFormat::PNG,
|
||||
width: None,
|
||||
height: None,
|
||||
quality: ResolvedImageQuality::High,
|
||||
deadline_ms: Some(1),
|
||||
},
|
||||
&test_media_limits(),
|
||||
),
|
||||
Err(MediaError::RequestTimeout)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_thumbnail_still_rejects_colour_signals_it_cannot_model() {
|
||||
let Some(cinema_transfer) = color_tagged_video("bt709", "smpte428", "bt709") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
assert_eq!(
|
||||
Some(MediaError::MediaDecodeFailed),
|
||||
extract_video_thumbnail(&hdr, OutputFormat::PNG, &test_media_limits()).err()
|
||||
extract_video_thumbnail(&cinema_transfer, OutputFormat::PNG, &test_media_limits()).err()
|
||||
);
|
||||
|
||||
let pq_only = color_tagged_video("bt709", "smpte2084", "bt709").expect("pq fixture");
|
||||
let cinema_primaries =
|
||||
color_tagged_video("smpte428", "bt709", "bt709").expect("smpte428 primaries fixture");
|
||||
assert_eq!(
|
||||
Some(MediaError::MediaDecodeFailed),
|
||||
extract_video_thumbnail(&pq_only, OutputFormat::PNG, &test_media_limits()).err()
|
||||
extract_video_thumbnail(&cinema_primaries, OutputFormat::PNG, &test_media_limits()).err()
|
||||
);
|
||||
}
|
||||
|
||||
let hlg = color_tagged_video("bt2020", "arib-std-b67", "bt709").expect("hlg fixture");
|
||||
assert_eq!(
|
||||
Some(MediaError::MediaDecodeFailed),
|
||||
extract_video_thumbnail(&hlg, OutputFormat::PNG, &test_media_limits()).err()
|
||||
fn flat_colour_tagged_video(
|
||||
colour: &str,
|
||||
primaries: &str,
|
||||
transfer: &str,
|
||||
matrix: &str,
|
||||
) -> Option<Vec<u8>> {
|
||||
let params =
|
||||
format!("setparams=color_primaries={primaries}:color_trc={transfer}:colorspace={matrix}");
|
||||
ffmpeg_gen_media(
|
||||
"fixture.mkv",
|
||||
&[
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
&format!("color=c={colour}:size=320x240:rate=10:duration=1"),
|
||||
"-vf",
|
||||
¶ms,
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-c:v",
|
||||
"mpeg4",
|
||||
"-qscale:v",
|
||||
"1",
|
||||
"-f",
|
||||
"matroska",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn flat_colour_centre_pixel(
|
||||
colour: &str,
|
||||
primaries: &str,
|
||||
transfer: &str,
|
||||
matrix: &str,
|
||||
) -> Option<[u8; 4]> {
|
||||
let video = flat_colour_tagged_video(colour, primaries, transfer, matrix)?;
|
||||
let thumb = extract_video_thumbnail(&video, OutputFormat::PNG, &test_media_limits())
|
||||
.unwrap_or_else(|error| panic!("{primaries}/{transfer}/{matrix} thumbnails: {error:?}"));
|
||||
let (width, height, rgba) = decode_rgba(&thumb.bytes);
|
||||
assert_eq!((320, 240), (width, height));
|
||||
let centre = ((height as usize / 2) * width as usize + width as usize / 2) * 4;
|
||||
Some([
|
||||
rgba[centre],
|
||||
rgba[centre + 1],
|
||||
rgba[centre + 2],
|
||||
rgba[centre + 3],
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_pq_video_frame_is_tone_mapped_rather_than_read_as_srgb() {
|
||||
let Some(sdr) = flat_colour_centre_pixel("0xc0c0c0", "bt709", "bt709", "bt709") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
let hdr = flat_colour_centre_pixel("0xc0c0c0", "bt2020", "smpte2084", "bt2020nc")
|
||||
.expect("pq fixture");
|
||||
assert!(
|
||||
hdr[0] > sdr[0] + 20,
|
||||
"a PQ highlight read as plain sRGB would land on the sdr value, got {hdr:?} against {sdr:?}"
|
||||
);
|
||||
assert_eq!(255, hdr[3], "tone mapped output stays opaque");
|
||||
}
|
||||
|
||||
let wide_primaries = color_tagged_video("bt2020", "bt709", "bt709").expect("bt2020 fixture");
|
||||
assert_eq!(
|
||||
Some(MediaError::MediaDecodeFailed),
|
||||
extract_video_thumbnail(&wide_primaries, OutputFormat::PNG, &test_media_limits()).err()
|
||||
#[test]
|
||||
fn an_hlg_video_frame_is_tone_mapped_rather_than_read_as_srgb() {
|
||||
let Some(sdr) = flat_colour_centre_pixel("0xc0c0c0", "bt709", "bt709", "bt709") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
let hdr = flat_colour_centre_pixel("0xc0c0c0", "bt2020", "arib-std-b67", "bt2020nc")
|
||||
.expect("hlg fixture");
|
||||
assert!(
|
||||
hdr[0] > sdr[0] + 20,
|
||||
"an HLG highlight read as plain sRGB would land on the sdr value, got {hdr:?} against {sdr:?}"
|
||||
);
|
||||
assert_eq!(255, hdr[3], "tone mapped output stays opaque");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bt2020_sdr_video_frame_is_converted_into_the_bt709_gamut() {
|
||||
let Some(wide) = flat_colour_centre_pixel("0x00ff00", "bt2020", "bt709", "bt709") else {
|
||||
eprintln!("skipping: ffmpeg CLI not available");
|
||||
return;
|
||||
};
|
||||
let narrow =
|
||||
flat_colour_centre_pixel("0x00ff00", "bt709", "bt709", "bt709").expect("bt709 fixture");
|
||||
let shift = (0..3)
|
||||
.map(|channel| wide[channel].abs_diff(narrow[channel]))
|
||||
.max()
|
||||
.expect("three channels");
|
||||
assert!(
|
||||
shift > 8,
|
||||
"bt2020 green {wide:?} should not match bt709 green {narrow:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,46 +2,74 @@
|
||||
|
||||
#include "native_shim_internal.h"
|
||||
|
||||
enum ff_sdr_transfer {
|
||||
FF_SDR_TRANSFER_SRGB = 0,
|
||||
FF_SDR_TRANSFER_LINEAR = 1,
|
||||
struct ff_frame_color_plan {
|
||||
int transfer;
|
||||
int gamut;
|
||||
};
|
||||
|
||||
static uint8_t ff_linear_to_srgb_lut[256];
|
||||
static pthread_once_t ff_transfer_lut_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static uint8_t ff_encode_srgb_byte(double linear) {
|
||||
double srgb = linear <= 0.0031308
|
||||
? 12.92 * linear
|
||||
: 1.055 * pow(linear, 1.0 / 2.4) - 0.055;
|
||||
long quantized = lround(srgb * 255.0);
|
||||
if (quantized < 0) quantized = 0;
|
||||
if (quantized > 255) quantized = 255;
|
||||
return (uint8_t)quantized;
|
||||
}
|
||||
|
||||
static void ff_initialize_transfer_luts(void) {
|
||||
for (int index = 0; index < 256; index++) {
|
||||
double encoded = (double)index / 255.0;
|
||||
ff_linear_to_srgb_lut[index] = ff_encode_srgb_byte(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t *ff_transfer_to_srgb_lut(enum ff_sdr_transfer transfer) {
|
||||
switch (transfer) {
|
||||
case FF_SDR_TRANSFER_LINEAR:
|
||||
return ff_linear_to_srgb_lut;
|
||||
static int ff_frame_color_gamut(const AVFrame *frame, int *out_gamut) {
|
||||
assert(frame != NULL);
|
||||
assert(out_gamut != NULL);
|
||||
switch (frame->color_primaries) {
|
||||
case AVCOL_PRI_UNSPECIFIED:
|
||||
case AVCOL_PRI_BT709:
|
||||
case AVCOL_PRI_BT470M:
|
||||
case AVCOL_PRI_BT470BG:
|
||||
case AVCOL_PRI_SMPTE170M:
|
||||
case AVCOL_PRI_SMPTE240M:
|
||||
case AVCOL_PRI_FILM:
|
||||
*out_gamut = FLUXER_HDR_GAMUT_SRGB;
|
||||
return 0;
|
||||
case AVCOL_PRI_BT2020:
|
||||
*out_gamut = FLUXER_HDR_GAMUT_BT2020;
|
||||
return 0;
|
||||
case AVCOL_PRI_SMPTE432:
|
||||
*out_gamut = FLUXER_HDR_GAMUT_DISPLAY_P3;
|
||||
return 0;
|
||||
default:
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int ff_frame_sdr_transfer(
|
||||
const AVFrame *frame,
|
||||
enum ff_sdr_transfer *out_transfer
|
||||
) {
|
||||
static int ff_frame_color_transfer(const AVFrame *frame, int *out_transfer) {
|
||||
assert(frame != NULL);
|
||||
assert(out_transfer != NULL);
|
||||
switch (frame->color_trc) {
|
||||
case AVCOL_TRC_UNSPECIFIED:
|
||||
case AVCOL_TRC_IEC61966_2_1:
|
||||
case AVCOL_TRC_GAMMA22:
|
||||
case AVCOL_TRC_GAMMA28:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_SRGB;
|
||||
return 0;
|
||||
case AVCOL_TRC_BT709:
|
||||
case AVCOL_TRC_SMPTE170M:
|
||||
case AVCOL_TRC_SMPTE240M:
|
||||
case AVCOL_TRC_BT2020_10:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_BT709;
|
||||
return 0;
|
||||
case AVCOL_TRC_BT2020_12:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_BT2020_12;
|
||||
return 0;
|
||||
case AVCOL_TRC_LINEAR:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_LINEAR;
|
||||
return 0;
|
||||
case AVCOL_TRC_SMPTE2084:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_PQ;
|
||||
return 0;
|
||||
case AVCOL_TRC_ARIB_STD_B67:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_HLG;
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int ff_frame_color_plan(
|
||||
const AVFrame *frame,
|
||||
struct ff_frame_color_plan *out_plan
|
||||
) {
|
||||
assert(frame != NULL);
|
||||
assert(out_plan != NULL);
|
||||
switch (frame->colorspace) {
|
||||
case AVCOL_SPC_RGB:
|
||||
case AVCOL_SPC_BT709:
|
||||
@@ -50,6 +78,7 @@ static int ff_frame_sdr_transfer(
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
@@ -59,75 +88,17 @@ static int ff_frame_sdr_transfer(
|
||||
frame->color_range != AVCOL_RANGE_JPEG) {
|
||||
return -1;
|
||||
}
|
||||
switch (frame->color_primaries) {
|
||||
case AVCOL_PRI_UNSPECIFIED:
|
||||
case AVCOL_PRI_BT709:
|
||||
case AVCOL_PRI_BT470M:
|
||||
case AVCOL_PRI_BT470BG:
|
||||
case AVCOL_PRI_SMPTE170M:
|
||||
case AVCOL_PRI_SMPTE240M:
|
||||
case AVCOL_PRI_FILM:
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
switch (frame->color_trc) {
|
||||
case AVCOL_TRC_UNSPECIFIED:
|
||||
case AVCOL_TRC_IEC61966_2_1:
|
||||
case AVCOL_TRC_GAMMA22:
|
||||
case AVCOL_TRC_GAMMA28:
|
||||
*out_transfer = FF_SDR_TRANSFER_SRGB;
|
||||
return 0;
|
||||
case AVCOL_TRC_BT709:
|
||||
case AVCOL_TRC_SMPTE170M:
|
||||
case AVCOL_TRC_SMPTE240M:
|
||||
*out_transfer = FF_SDR_TRANSFER_SRGB;
|
||||
return 0;
|
||||
case AVCOL_TRC_LINEAR:
|
||||
*out_transfer = FF_SDR_TRANSFER_LINEAR;
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
if (ff_frame_color_gamut(frame, &out_plan->gamut) != 0) return -1;
|
||||
return ff_frame_color_transfer(frame, &out_plan->transfer);
|
||||
}
|
||||
|
||||
static int ff_convert_rgba_transfer_to_srgb(
|
||||
enum ff_sdr_transfer transfer,
|
||||
uint8_t *data,
|
||||
int width,
|
||||
int height,
|
||||
long long deadline_monotonic_ms
|
||||
static int ff_frame_plan_is_passthrough(
|
||||
const struct ff_frame_color_plan *plan
|
||||
) {
|
||||
assert(data != NULL);
|
||||
assert(width > 0);
|
||||
assert(height > 0);
|
||||
if (deadline_monotonic_ms < 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
if (pthread_once(
|
||||
&ff_transfer_lut_once,
|
||||
ff_initialize_transfer_luts) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
const uint8_t *lut = ff_transfer_to_srgb_lut(transfer);
|
||||
if (lut == NULL) {
|
||||
return fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
}
|
||||
size_t row_bytes = (size_t)width * 4u;
|
||||
for (int row = 0; row < height; row++) {
|
||||
if (row % FLUXER_VIDEO_DEADLINE_ROWS == 0) {
|
||||
int status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
}
|
||||
uint8_t *row_data = data + (size_t)row * row_bytes;
|
||||
for (int column = 0; column < width; column++) {
|
||||
uint8_t *pixel = row_data + (size_t)column * 4u;
|
||||
pixel[0] = lut[pixel[0]];
|
||||
pixel[1] = lut[pixel[1]];
|
||||
pixel[2] = lut[pixel[2]];
|
||||
}
|
||||
}
|
||||
return fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
assert(plan != NULL);
|
||||
return plan->gamut == FLUXER_HDR_GAMUT_SRGB &&
|
||||
!fluxer_hdr_transfer_is_hdr(plan->transfer) &&
|
||||
plan->transfer != FLUXER_HDR_TRANSFER_LINEAR;
|
||||
}
|
||||
|
||||
typedef int (*ff_i420_to_abgr_fn)(
|
||||
@@ -150,6 +121,8 @@ static int ff_swscale_colorspace(enum AVColorSpace colorspace) {
|
||||
return SWS_CS_SMPTE170M;
|
||||
case AVCOL_SPC_SMPTE240M:
|
||||
return SWS_CS_SMPTE240M;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
return SWS_CS_BT2020;
|
||||
default:
|
||||
return SWS_CS_DEFAULT;
|
||||
}
|
||||
@@ -331,6 +304,7 @@ static int ff_swscale_frame_to_rgba(
|
||||
int source_height,
|
||||
int output_width,
|
||||
int output_height,
|
||||
int destination_pixel_bytes,
|
||||
long long deadline_monotonic_ms,
|
||||
uint8_t *dst
|
||||
) {
|
||||
@@ -339,6 +313,7 @@ static int ff_swscale_frame_to_rgba(
|
||||
assert(source_height > 0);
|
||||
assert(output_width > 0);
|
||||
assert(output_height > 0);
|
||||
assert(destination_pixel_bytes == 4 || destination_pixel_bytes == 8);
|
||||
assert(dst != NULL);
|
||||
const AVPixFmtDescriptor *descriptor = av_pix_fmt_desc_get(frame->format);
|
||||
if (descriptor == NULL) return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
@@ -350,7 +325,7 @@ static int ff_swscale_frame_to_rgba(
|
||||
if (source_height < 2) return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
uint8_t *dst_data[4] = { dst, NULL, NULL, NULL };
|
||||
int dst_linesize[4] = { output_width * 4, 0, 0, 0 };
|
||||
int dst_linesize[4] = { output_width * destination_pixel_bytes, 0, 0, 0 };
|
||||
int output_rows = 0;
|
||||
for (int source_row = 0; source_row < source_height;) {
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
@@ -395,6 +370,54 @@ static int ff_swscale_frame_to_rgba(
|
||||
: FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
|
||||
static int ff_convert_hdr_frame_to_rgba(
|
||||
AVFrame *frame,
|
||||
int source_width,
|
||||
int source_height,
|
||||
int output_width,
|
||||
int output_height,
|
||||
struct SwsContext **sws,
|
||||
long long deadline_monotonic_ms,
|
||||
uint8_t *dst,
|
||||
size_t rgba_size,
|
||||
const struct ff_frame_color_plan *plan
|
||||
) {
|
||||
assert(sws != NULL);
|
||||
assert(dst != NULL);
|
||||
assert(plan != NULL);
|
||||
if (rgba_size > FLUXER_MAX_VIDEO_RGBA_BYTES / 2u) {
|
||||
return FLUXER_NATIVE_STATUS_INVALID_DIMENSIONS;
|
||||
}
|
||||
uint8_t *staging = (uint8_t *)g_try_malloc(rgba_size * 2u);
|
||||
if (staging == NULL) return FLUXER_NATIVE_STATUS_ALLOCATION_FAILED;
|
||||
*sws = sws_getCachedContext(*sws,
|
||||
source_width, source_height,
|
||||
(enum AVPixelFormat)frame->format,
|
||||
output_width, output_height,
|
||||
AV_PIX_FMT_RGBA64LE,
|
||||
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
int status = FLUXER_NATIVE_STATUS_OK;
|
||||
if (*sws == NULL) {
|
||||
status = FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
} else if (ff_configure_swscale_color(*sws, frame) != 0) {
|
||||
status = FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
} else {
|
||||
status = ff_swscale_frame_to_rgba(
|
||||
*sws, frame, source_height, output_width, output_height, 8,
|
||||
deadline_monotonic_ms, staging);
|
||||
}
|
||||
if (status == FLUXER_NATIVE_STATUS_OK) {
|
||||
status = fluxer_hdr_tone_map_rgba16(
|
||||
staging, (size_t)output_width * 8u,
|
||||
dst, (size_t)output_width * 4u,
|
||||
output_width, output_height, 16,
|
||||
plan->gamut, plan->transfer, FLUXER_VIDEO_DEADLINE_ROWS,
|
||||
deadline_monotonic_ms);
|
||||
}
|
||||
g_free(staging);
|
||||
return status;
|
||||
}
|
||||
|
||||
int fluxer_av_frame_convert_to_rgba(
|
||||
AVFrame *frame,
|
||||
int source_width,
|
||||
@@ -411,43 +434,43 @@ int fluxer_av_frame_convert_to_rgba(
|
||||
}
|
||||
int status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
size_t rgba_size = 0;
|
||||
if (ff_validate_rgba_geometry(source_width, source_height, NULL) != 0 ||
|
||||
ff_validate_rgba_geometry(output_width, output_height, NULL) != 0) {
|
||||
ff_validate_rgba_geometry(output_width, output_height, &rgba_size) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_INVALID_DIMENSIONS;
|
||||
}
|
||||
enum ff_sdr_transfer transfer = FF_SDR_TRANSFER_SRGB;
|
||||
if (ff_frame_sdr_transfer(frame, &transfer) != 0) {
|
||||
struct ff_frame_color_plan plan;
|
||||
if (ff_frame_color_plan(frame, &plan) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if (fluxer_hdr_transfer_is_hdr(plan.transfer)) {
|
||||
return ff_convert_hdr_frame_to_rgba(
|
||||
frame, source_width, source_height, output_width, output_height,
|
||||
sws, deadline_monotonic_ms, dst, rgba_size, &plan);
|
||||
}
|
||||
int libyuv_applied = 0;
|
||||
status = ff_convert_i420_frame_to_rgba_libyuv(
|
||||
frame, source_width, source_height, output_width, output_height,
|
||||
deadline_monotonic_ms, dst, &libyuv_applied);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
if (libyuv_applied) {
|
||||
if (transfer != FF_SDR_TRANSFER_SRGB) {
|
||||
return ff_convert_rgba_transfer_to_srgb(
|
||||
transfer, dst, output_width, output_height,
|
||||
deadline_monotonic_ms);
|
||||
if (!libyuv_applied) {
|
||||
*sws = sws_getCachedContext(*sws,
|
||||
source_width, source_height,
|
||||
(enum AVPixelFormat)frame->format,
|
||||
output_width, output_height,
|
||||
AV_PIX_FMT_RGBA,
|
||||
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
if (*sws == NULL) return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
if (ff_configure_swscale_color(*sws, frame) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
status = ff_swscale_frame_to_rgba(
|
||||
*sws, frame, source_height, output_width, output_height, 4,
|
||||
deadline_monotonic_ms, dst);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
}
|
||||
*sws = sws_getCachedContext(*sws,
|
||||
source_width, source_height,
|
||||
(enum AVPixelFormat)frame->format,
|
||||
output_width, output_height, AV_PIX_FMT_RGBA,
|
||||
SWS_FAST_BILINEAR, NULL, NULL, NULL);
|
||||
if (*sws == NULL) return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
if (ff_configure_swscale_color(*sws, frame) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
status = ff_swscale_frame_to_rgba(
|
||||
*sws, frame, source_height, output_width, output_height,
|
||||
deadline_monotonic_ms, dst);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
if (transfer != FF_SDR_TRANSFER_SRGB) {
|
||||
return ff_convert_rgba_transfer_to_srgb(
|
||||
transfer, dst, output_width, output_height, deadline_monotonic_ms);
|
||||
}
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
if (ff_frame_plan_is_passthrough(&plan)) return FLUXER_NATIVE_STATUS_OK;
|
||||
return fluxer_hdr_apply_sdr_gamut(
|
||||
dst, output_width, output_height, plan.gamut, plan.transfer,
|
||||
FLUXER_VIDEO_DEADLINE_ROWS, deadline_monotonic_ms);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#include "native_shim_internal.h"
|
||||
|
||||
float fluxer_pq_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
float fluxer_hlg_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
float fluxer_hlg_ootf_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
float fluxer_pq_tone_scale_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
float fluxer_hlg_tone_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
uint8_t fluxer_srgb_lut[FLUXER_HDR_SRGB_LUT_SIZE];
|
||||
float fluxer_pq_sdr_target_perceptual;
|
||||
float fluxer_hlg_source_peak_perceptual;
|
||||
float fluxer_hlg_sdr_target_perceptual;
|
||||
|
||||
static pthread_once_t fluxer_hdr_lut_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static void fluxer_init_hdr_luts(void) {
|
||||
const double m1 = 0.1593017578125;
|
||||
const double m2 = 78.84375;
|
||||
const double c1 = 0.8359375;
|
||||
const double c2 = 18.8515625;
|
||||
const double c3 = 18.6875;
|
||||
for (int index = 0; index < FLUXER_HDR_PQ_LUT_SIZE; index++) {
|
||||
double encoded = (double)index / (FLUXER_HDR_PQ_LUT_SIZE - 1);
|
||||
double encoded_power = pow(encoded, 1.0 / m2);
|
||||
double numerator = encoded_power - c1;
|
||||
if (numerator < 0.0) numerator = 0.0;
|
||||
double denominator = c2 - c3 * encoded_power;
|
||||
double luminance = denominator > 0.0
|
||||
? pow(numerator / denominator, 1.0 / m1)
|
||||
: 0.0;
|
||||
if (luminance < 0.0) luminance = 0.0;
|
||||
if (luminance > 1.0) luminance = 1.0;
|
||||
fluxer_pq_lut[index] = (float)luminance;
|
||||
}
|
||||
const double a = 0.17883277;
|
||||
const double b = 0.28466892;
|
||||
const double c = 0.55991073;
|
||||
for (int index = 0; index < FLUXER_HDR_HLG_LUT_SIZE; index++) {
|
||||
double encoded = (double)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
double scene = encoded <= 0.5
|
||||
? (encoded * encoded) / 3.0
|
||||
: (exp((encoded - c) / a) + b) / 12.0;
|
||||
if (scene < 0.0) scene = 0.0;
|
||||
if (scene > 1.0) scene = 1.0;
|
||||
fluxer_hlg_lut[index] = (float)scene;
|
||||
double normalized = (double)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
fluxer_hlg_ootf_scale_lut[index] = normalized > 0.0
|
||||
? (float)pow(normalized, 0.2)
|
||||
: 0.0f;
|
||||
}
|
||||
fluxer_pq_sdr_target_perceptual =
|
||||
fluxer_pq_oetf(FLUXER_PQ_SDR_TARGET_NORM);
|
||||
fluxer_hlg_source_peak_perceptual =
|
||||
fluxer_pq_oetf(FLUXER_HLG_REFERENCE_PEAK_NORM);
|
||||
fluxer_hlg_sdr_target_perceptual =
|
||||
fluxer_pq_sdr_target_perceptual /
|
||||
fluxer_hlg_source_peak_perceptual;
|
||||
for (int index = 0; index < FLUXER_HDR_PQ_LUT_SIZE; index++) {
|
||||
fluxer_pq_tone_scale_lut[index] = fluxer_hdr_tone_scale(
|
||||
fluxer_pq_lut[index], FLUXER_PQ_SDR_TARGET_NORM,
|
||||
1.0f,
|
||||
fluxer_pq_sdr_target_perceptual);
|
||||
}
|
||||
for (int index = 0; index < FLUXER_HDR_HLG_LUT_SIZE; index++) {
|
||||
float maximum = (float)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
float absolute_maximum =
|
||||
maximum * FLUXER_HLG_REFERENCE_PEAK_NORM;
|
||||
fluxer_hlg_tone_scale_lut[index] =
|
||||
FLUXER_HLG_REFERENCE_PEAK_NORM * fluxer_hdr_tone_scale(
|
||||
absolute_maximum, FLUXER_PQ_SDR_TARGET_NORM,
|
||||
fluxer_hlg_source_peak_perceptual,
|
||||
fluxer_hlg_sdr_target_perceptual);
|
||||
}
|
||||
for (int index = 0; index < FLUXER_HDR_SRGB_LUT_SIZE; index++) {
|
||||
float linear = (float)index / (FLUXER_HDR_SRGB_LUT_SIZE - 1);
|
||||
fluxer_srgb_lut[index] = fluxer_quantize8(
|
||||
fluxer_srgb_oetf(linear));
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t fluxer_hdr_read_le16(const uint8_t *value) {
|
||||
return (uint16_t)((uint16_t)value[0] | ((uint16_t)value[1] << 8));
|
||||
}
|
||||
|
||||
int fluxer_hdr_luts_ready(void) {
|
||||
if (pthread_once(&fluxer_hdr_lut_once, fluxer_init_hdr_luts) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
}
|
||||
|
||||
int fluxer_hdr_transfer_is_hdr(int transfer) {
|
||||
return transfer == FLUXER_HDR_TRANSFER_PQ ||
|
||||
transfer == FLUXER_HDR_TRANSFER_HLG;
|
||||
}
|
||||
|
||||
int fluxer_hdr_apply_sdr_gamut(
|
||||
uint8_t *rgba,
|
||||
int width,
|
||||
int height,
|
||||
int gamut,
|
||||
int transfer,
|
||||
int deadline_rows,
|
||||
long long deadline_monotonic_ms
|
||||
) {
|
||||
if (rgba == NULL || width <= 0 || height <= 0 || deadline_rows <= 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
if (fluxer_hdr_transfer_is_hdr(transfer)) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if (transfer != FLUXER_HDR_TRANSFER_SRGB &&
|
||||
transfer != FLUXER_HDR_TRANSFER_BT709 &&
|
||||
transfer != FLUXER_HDR_TRANSFER_BT2020_12 &&
|
||||
transfer != FLUXER_HDR_TRANSFER_LINEAR) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if (transfer == FLUXER_HDR_TRANSFER_SRGB &&
|
||||
gamut == FLUXER_HDR_GAMUT_SRGB) {
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
}
|
||||
int status = fluxer_hdr_luts_ready();
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
size_t row_bytes = (size_t)width * 4u;
|
||||
for (int row = 0; row < height; row++) {
|
||||
if (row % deadline_rows == 0) {
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
}
|
||||
uint8_t *row_data = rgba + (size_t)row * row_bytes;
|
||||
for (int column = 0; column < width; column++) {
|
||||
uint8_t *pixel = row_data + (size_t)column * 4u;
|
||||
float red = (float)pixel[0] / 255.0f;
|
||||
float green = (float)pixel[1] / 255.0f;
|
||||
float blue = (float)pixel[2] / 255.0f;
|
||||
if (transfer == FLUXER_HDR_TRANSFER_SRGB) {
|
||||
red = fluxer_inverse_srgb(red);
|
||||
green = fluxer_inverse_srgb(green);
|
||||
blue = fluxer_inverse_srgb(blue);
|
||||
} else if (transfer == FLUXER_HDR_TRANSFER_BT709) {
|
||||
red = fluxer_inverse_bt709(red);
|
||||
green = fluxer_inverse_bt709(green);
|
||||
blue = fluxer_inverse_bt709(blue);
|
||||
} else if (transfer == FLUXER_HDR_TRANSFER_BT2020_12) {
|
||||
red = fluxer_inverse_bt2020_12(red);
|
||||
green = fluxer_inverse_bt2020_12(green);
|
||||
blue = fluxer_inverse_bt2020_12(blue);
|
||||
}
|
||||
float srgb_red = 0.0f;
|
||||
float srgb_green = 0.0f;
|
||||
float srgb_blue = 0.0f;
|
||||
fluxer_hdr_convert_gamut_linear(
|
||||
gamut, red, green, blue,
|
||||
&srgb_red, &srgb_green, &srgb_blue);
|
||||
pixel[0] = fluxer_quantize8(fluxer_srgb_oetf(srgb_red));
|
||||
pixel[1] = fluxer_quantize8(fluxer_srgb_oetf(srgb_green));
|
||||
pixel[2] = fluxer_quantize8(fluxer_srgb_oetf(srgb_blue));
|
||||
}
|
||||
}
|
||||
return fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
}
|
||||
|
||||
int fluxer_hdr_tone_map_rgba16(
|
||||
const uint8_t *source,
|
||||
size_t source_stride,
|
||||
uint8_t *destination,
|
||||
size_t destination_stride,
|
||||
int width,
|
||||
int height,
|
||||
int bit_depth,
|
||||
int gamut,
|
||||
int transfer,
|
||||
int deadline_rows,
|
||||
long long deadline_monotonic_ms
|
||||
) {
|
||||
if (source == NULL || destination == NULL || width <= 0 || height <= 0 ||
|
||||
deadline_rows <= 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
if (bit_depth != 10 && bit_depth != 12 && bit_depth != 16) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if (!fluxer_hdr_transfer_is_hdr(transfer)) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if ((size_t)width > SIZE_MAX / 8u) {
|
||||
return FLUXER_NATIVE_STATUS_INVALID_DIMENSIONS;
|
||||
}
|
||||
if (source_stride < (size_t)width * 8u ||
|
||||
destination_stride < (size_t)width * 4u) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
int status = fluxer_hdr_luts_ready();
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
int is_hlg = transfer == FLUXER_HDR_TRANSFER_HLG;
|
||||
unsigned int mask = bit_depth == 16
|
||||
? 0xffffu
|
||||
: (unsigned int)((1u << bit_depth) - 1u);
|
||||
const float *linear_lut = is_hlg ? fluxer_hlg_lut : fluxer_pq_lut;
|
||||
const float *tone_scale_lut =
|
||||
is_hlg ? fluxer_hlg_tone_scale_lut : fluxer_pq_tone_scale_lut;
|
||||
for (int row = 0; row < height; row++) {
|
||||
if (row % deadline_rows == 0) {
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
}
|
||||
const uint8_t *source_row = source + (size_t)row * source_stride;
|
||||
uint8_t *destination_row =
|
||||
destination + (size_t)row * destination_stride;
|
||||
for (int column = 0; column < width; column++) {
|
||||
const uint8_t *source_pixel = source_row + (size_t)column * 8u;
|
||||
uint16_t red_code =
|
||||
(uint16_t)(fluxer_hdr_read_le16(source_pixel) & mask);
|
||||
uint16_t green_code =
|
||||
(uint16_t)(fluxer_hdr_read_le16(source_pixel + 2) & mask);
|
||||
uint16_t blue_code =
|
||||
(uint16_t)(fluxer_hdr_read_le16(source_pixel + 4) & mask);
|
||||
uint16_t red_index = fluxer_hdr_lut_index(red_code, bit_depth);
|
||||
uint16_t green_index = fluxer_hdr_lut_index(green_code, bit_depth);
|
||||
uint16_t blue_index = fluxer_hdr_lut_index(blue_code, bit_depth);
|
||||
float red = linear_lut[red_index];
|
||||
float green = linear_lut[green_index];
|
||||
float blue = linear_lut[blue_index];
|
||||
uint16_t maximum_index = red_index;
|
||||
if (green_index > maximum_index) maximum_index = green_index;
|
||||
if (blue_index > maximum_index) maximum_index = blue_index;
|
||||
if (is_hlg) {
|
||||
float luma = fluxer_hdr_linear_luma(gamut, red, green, blue);
|
||||
float ootf_scale =
|
||||
fluxer_hlg_ootf_scale_lut[fluxer_unit_lut_index(luma)];
|
||||
red *= ootf_scale;
|
||||
green *= ootf_scale;
|
||||
blue *= ootf_scale;
|
||||
float maximum = fmaxf(red, fmaxf(green, blue));
|
||||
maximum_index = fluxer_unit_lut_index(maximum);
|
||||
}
|
||||
float scale = tone_scale_lut[maximum_index];
|
||||
uint8_t *destination_pixel =
|
||||
destination_row + (size_t)column * 4u;
|
||||
fluxer_hdr_pipeline_pixel(
|
||||
red, green, blue, scale, gamut, destination_pixel);
|
||||
unsigned int alpha =
|
||||
(unsigned int)fluxer_hdr_read_le16(source_pixel + 6) & mask;
|
||||
destination_pixel[3] = (uint8_t)(
|
||||
(alpha * 255u + (mask >> 1)) / mask);
|
||||
}
|
||||
}
|
||||
return fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vips_shim.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define FLUXER_HDR_PQ_LUT_SIZE 4096
|
||||
#define FLUXER_HDR_HLG_LUT_SIZE 4096
|
||||
#define FLUXER_HDR_SRGB_LUT_SIZE 4096
|
||||
#define FLUXER_PQ_SDR_TARGET_NORM 0.0203f
|
||||
#define FLUXER_HLG_REFERENCE_PEAK_NORM 0.1f
|
||||
|
||||
enum fluxer_hdr_gamut {
|
||||
FLUXER_HDR_GAMUT_SRGB = 0,
|
||||
FLUXER_HDR_GAMUT_BT2020 = 1,
|
||||
FLUXER_HDR_GAMUT_DISPLAY_P3 = 2,
|
||||
};
|
||||
|
||||
enum fluxer_hdr_transfer {
|
||||
FLUXER_HDR_TRANSFER_SRGB = 0,
|
||||
FLUXER_HDR_TRANSFER_BT709 = 1,
|
||||
FLUXER_HDR_TRANSFER_BT2020_12 = 2,
|
||||
FLUXER_HDR_TRANSFER_LINEAR = 3,
|
||||
FLUXER_HDR_TRANSFER_PQ = 4,
|
||||
FLUXER_HDR_TRANSFER_HLG = 5,
|
||||
};
|
||||
|
||||
extern float fluxer_pq_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
extern float fluxer_hlg_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
extern float fluxer_hlg_ootf_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
extern float fluxer_pq_tone_scale_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
extern float fluxer_hlg_tone_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
extern uint8_t fluxer_srgb_lut[FLUXER_HDR_SRGB_LUT_SIZE];
|
||||
extern float fluxer_pq_sdr_target_perceptual;
|
||||
extern float fluxer_hlg_source_peak_perceptual;
|
||||
extern float fluxer_hlg_sdr_target_perceptual;
|
||||
|
||||
int fluxer_hdr_luts_ready(void);
|
||||
int fluxer_hdr_transfer_is_hdr(int transfer);
|
||||
int fluxer_hdr_apply_sdr_gamut(uint8_t *rgba, int width, int height, int gamut,
|
||||
int transfer, int deadline_rows,
|
||||
long long deadline_monotonic_ms);
|
||||
int fluxer_hdr_tone_map_rgba16(const uint8_t *source, size_t source_stride,
|
||||
uint8_t *destination, size_t destination_stride,
|
||||
int width, int height, int bit_depth, int gamut,
|
||||
int transfer, int deadline_rows,
|
||||
long long deadline_monotonic_ms);
|
||||
|
||||
static inline uint16_t fluxer_hdr_lut_index(uint16_t code, int bit_depth) {
|
||||
assert(bit_depth == 10 || bit_depth == 12 || bit_depth == 16);
|
||||
if (bit_depth == 16) return (uint16_t)(code >> 4);
|
||||
if (bit_depth == 12) return code & 0x0fffu;
|
||||
uint16_t code10 = code & 0x03ffu;
|
||||
return (uint16_t)((code10 << 2) | (code10 >> 8));
|
||||
}
|
||||
|
||||
static inline uint16_t fluxer_unit_lut_index(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return FLUXER_HDR_HLG_LUT_SIZE - 1;
|
||||
return (uint16_t)(
|
||||
value * (FLUXER_HDR_HLG_LUT_SIZE - 1) + 0.5f);
|
||||
}
|
||||
|
||||
static inline float fluxer_bt2390_eetf_perceptual(
|
||||
float encoded,
|
||||
float max_luminance
|
||||
) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (max_luminance >= 1.0f) {
|
||||
return encoded > 1.0f ? 1.0f : encoded;
|
||||
}
|
||||
float knee = 1.5f * max_luminance - 0.5f;
|
||||
if (encoded < knee) return encoded;
|
||||
if (encoded >= 1.0f) return max_luminance;
|
||||
float position = (encoded - knee) / (1.0f - knee);
|
||||
float squared = position * position;
|
||||
float cubed = squared * position;
|
||||
float start_basis = 2.0f * cubed - 3.0f * squared + 1.0f;
|
||||
float tangent_basis = cubed - 2.0f * squared + position;
|
||||
float end_basis = -2.0f * cubed + 3.0f * squared;
|
||||
float mapped = start_basis * knee + tangent_basis * (1.0f - knee) +
|
||||
end_basis * max_luminance;
|
||||
if (mapped > max_luminance) mapped = max_luminance;
|
||||
if (mapped < 0.0f) mapped = 0.0f;
|
||||
return mapped;
|
||||
}
|
||||
|
||||
static inline float fluxer_pq_oetf(float luminance) {
|
||||
if (luminance <= 0.0f) return 0.0f;
|
||||
if (luminance >= 1.0f) luminance = 1.0f;
|
||||
const float m1 = 0.1593017578125f;
|
||||
const float m2 = 78.84375f;
|
||||
const float c1 = 0.8359375f;
|
||||
const float c2 = 18.8515625f;
|
||||
const float c3 = 18.6875f;
|
||||
float power = powf(luminance, m1);
|
||||
return powf((c1 + c2 * power) / (1.0f + c3 * power), m2);
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_pq(float encoded) {
|
||||
const float m1 = 0.1593017578125f;
|
||||
const float m2 = 78.84375f;
|
||||
const float c1 = 0.8359375f;
|
||||
const float c2 = 18.8515625f;
|
||||
const float c3 = 18.6875f;
|
||||
float power = powf(encoded, 1.0f / m2);
|
||||
float numerator = power - c1;
|
||||
if (numerator < 0.0f) numerator = 0.0f;
|
||||
float denominator = c2 - c3 * power;
|
||||
if (denominator <= 0.0f) return 0.0f;
|
||||
float luminance = powf(numerator / denominator, 1.0f / m1);
|
||||
return luminance < 0.0f ? 0.0f : luminance;
|
||||
}
|
||||
|
||||
static inline float fluxer_srgb_oetf(float value) {
|
||||
if (value <= 0.0f) return 0.0f;
|
||||
if (value >= 1.0f) return 1.0f;
|
||||
if (value <= 0.0031308f) return 12.92f * value;
|
||||
return 1.055f * powf(value, 1.0f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
static inline uint8_t fluxer_quantize8(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return 255;
|
||||
int quantized = (int)(value * 255.0f + 0.5f);
|
||||
if (quantized < 0) return 0;
|
||||
if (quantized > 255) return 255;
|
||||
return (uint8_t)quantized;
|
||||
}
|
||||
|
||||
static inline uint8_t fluxer_srgb_lut_quantize(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return 255;
|
||||
size_t index = (size_t)(
|
||||
value * (FLUXER_HDR_SRGB_LUT_SIZE - 1) + 0.5f);
|
||||
assert(index < FLUXER_HDR_SRGB_LUT_SIZE);
|
||||
return fluxer_srgb_lut[index];
|
||||
}
|
||||
|
||||
static inline void fluxer_bt2020_to_bt709_linear(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
*out_red = 1.6605f * red - 0.5876f * green - 0.0728f * blue;
|
||||
*out_green = -0.1246f * red + 1.1329f * green - 0.0083f * blue;
|
||||
*out_blue = -0.0182f * red - 0.1006f * green + 1.1187f * blue;
|
||||
}
|
||||
|
||||
static inline void fluxer_display_p3_to_srgb_linear(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
*out_red = 1.2249401f * red - 0.2249404f * green;
|
||||
*out_green = -0.0420569f * red + 1.0420571f * green;
|
||||
*out_blue = -0.0196376f * red - 0.0786361f * green + 1.0982735f * blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_srgb(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded <= 0.04045f) return encoded / 12.92f;
|
||||
return powf((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_bt709(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded < 0.081f) return encoded / 4.5f;
|
||||
return powf((encoded + 0.099f) / 1.099f, 1.0f / 0.45f);
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_bt2020_12(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded < 0.08145f) return encoded / 4.5f;
|
||||
return powf((encoded + 0.0993f) / 1.0993f, 1.0f / 0.45f);
|
||||
}
|
||||
|
||||
static inline void fluxer_hdr_convert_gamut_linear(
|
||||
int gamut,
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
if (gamut == FLUXER_HDR_GAMUT_BT2020) {
|
||||
fluxer_bt2020_to_bt709_linear(
|
||||
red, green, blue, out_red, out_green, out_blue);
|
||||
return;
|
||||
}
|
||||
if (gamut == FLUXER_HDR_GAMUT_DISPLAY_P3) {
|
||||
fluxer_display_p3_to_srgb_linear(
|
||||
red, green, blue, out_red, out_green, out_blue);
|
||||
return;
|
||||
}
|
||||
assert(gamut == FLUXER_HDR_GAMUT_SRGB);
|
||||
*out_red = red;
|
||||
*out_green = green;
|
||||
*out_blue = blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_hdr_linear_luma(
|
||||
int gamut,
|
||||
float red,
|
||||
float green,
|
||||
float blue
|
||||
) {
|
||||
if (gamut == FLUXER_HDR_GAMUT_BT2020) {
|
||||
return 0.2627f * red + 0.6780f * green + 0.0593f * blue;
|
||||
}
|
||||
if (gamut == FLUXER_HDR_GAMUT_DISPLAY_P3) {
|
||||
return 0.2289746f * red + 0.6917385f * green + 0.0792869f * blue;
|
||||
}
|
||||
assert(gamut == FLUXER_HDR_GAMUT_SRGB);
|
||||
return 0.2126f * red + 0.7152f * green + 0.0722f * blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_hdr_tone_scale(
|
||||
float maximum,
|
||||
float target_normalized,
|
||||
float source_peak_perceptual,
|
||||
float target_perceptual
|
||||
) {
|
||||
if (maximum <= 0.0f) return 0.0f;
|
||||
assert(source_peak_perceptual > 0.0f);
|
||||
assert(source_peak_perceptual <= 1.0f);
|
||||
float perceptual = fluxer_pq_oetf(maximum) /
|
||||
source_peak_perceptual;
|
||||
float mapped_perceptual = fluxer_bt2390_eetf_perceptual(
|
||||
perceptual, target_perceptual);
|
||||
float mapped = fluxer_inverse_pq(
|
||||
mapped_perceptual * source_peak_perceptual);
|
||||
return (mapped / maximum) / target_normalized;
|
||||
}
|
||||
|
||||
static inline void fluxer_hdr_pipeline_pixel(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float scale,
|
||||
int gamut,
|
||||
uint8_t *output
|
||||
) {
|
||||
float display_red = red * scale;
|
||||
float display_green = green * scale;
|
||||
float display_blue = blue * scale;
|
||||
if (display_red < 0.0f) display_red = 0.0f;
|
||||
if (display_green < 0.0f) display_green = 0.0f;
|
||||
if (display_blue < 0.0f) display_blue = 0.0f;
|
||||
if (display_red > 1.0f) display_red = 1.0f;
|
||||
if (display_green > 1.0f) display_green = 1.0f;
|
||||
if (display_blue > 1.0f) display_blue = 1.0f;
|
||||
float linear_red = display_red;
|
||||
float linear_green = display_green;
|
||||
float linear_blue = display_blue;
|
||||
fluxer_hdr_convert_gamut_linear(
|
||||
gamut, display_red, display_green, display_blue,
|
||||
&linear_red, &linear_green, &linear_blue);
|
||||
if (linear_red < 0.0f) linear_red = 0.0f;
|
||||
if (linear_green < 0.0f) linear_green = 0.0f;
|
||||
if (linear_blue < 0.0f) linear_blue = 0.0f;
|
||||
if (linear_red > 1.0f) linear_red = 1.0f;
|
||||
if (linear_green > 1.0f) linear_green = 1.0f;
|
||||
if (linear_blue > 1.0f) linear_blue = 1.0f;
|
||||
output[0] = fluxer_srgb_lut_quantize(linear_red);
|
||||
output[1] = fluxer_srgb_lut_quantize(linear_green);
|
||||
output[2] = fluxer_srgb_lut_quantize(linear_blue);
|
||||
}
|
||||
@@ -2,341 +2,10 @@
|
||||
|
||||
#include "native_shim_internal.h"
|
||||
|
||||
#define FLUXER_HDR_PQ_LUT_SIZE 4096
|
||||
#define FLUXER_HDR_HLG_LUT_SIZE 4096
|
||||
#define FLUXER_HDR_SRGB_LUT_SIZE 4096
|
||||
#define FLUXER_PQ_SDR_TARGET_NORM 0.0203f
|
||||
#define FLUXER_HLG_REFERENCE_PEAK_NORM 0.1f
|
||||
#define FLUXER_HEIF_MAX_AUXILIARY_IMAGES 4096
|
||||
#define FLUXER_HEIF_DEADLINE_ROWS 64
|
||||
#define FLUXER_HEIF_ICC_PROFILE_BYTES_MAX ((size_t)4 * 1024 * 1024)
|
||||
|
||||
enum fluxer_heif_gamut {
|
||||
FLUXER_HEIF_GAMUT_SRGB = 0,
|
||||
FLUXER_HEIF_GAMUT_BT2020 = 1,
|
||||
FLUXER_HEIF_GAMUT_DISPLAY_P3 = 2,
|
||||
};
|
||||
|
||||
static float fluxer_pq_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
static float fluxer_hlg_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
static float fluxer_hlg_ootf_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
static float fluxer_pq_tone_scale_lut[FLUXER_HDR_PQ_LUT_SIZE];
|
||||
static float fluxer_hlg_tone_scale_lut[FLUXER_HDR_HLG_LUT_SIZE];
|
||||
static uint8_t fluxer_srgb_lut[FLUXER_HDR_SRGB_LUT_SIZE];
|
||||
static pthread_once_t fluxer_hdr_lut_once = PTHREAD_ONCE_INIT;
|
||||
static float fluxer_pq_sdr_target_perceptual;
|
||||
static float fluxer_hlg_source_peak_perceptual;
|
||||
static float fluxer_hlg_sdr_target_perceptual;
|
||||
|
||||
static inline float fluxer_pq_oetf(float luminance);
|
||||
static inline float fluxer_hdr_tone_scale(
|
||||
float maximum,
|
||||
float target_normalized,
|
||||
float source_peak_perceptual,
|
||||
float target_perceptual
|
||||
);
|
||||
static inline float fluxer_srgb_oetf(float value);
|
||||
static inline uint8_t fluxer_quantize8(float value);
|
||||
|
||||
static void fluxer_init_hdr_luts(void) {
|
||||
const double m1 = 0.1593017578125;
|
||||
const double m2 = 78.84375;
|
||||
const double c1 = 0.8359375;
|
||||
const double c2 = 18.8515625;
|
||||
const double c3 = 18.6875;
|
||||
for (int index = 0; index < FLUXER_HDR_PQ_LUT_SIZE; index++) {
|
||||
double encoded = (double)index / (FLUXER_HDR_PQ_LUT_SIZE - 1);
|
||||
double encoded_power = pow(encoded, 1.0 / m2);
|
||||
double numerator = encoded_power - c1;
|
||||
if (numerator < 0.0) numerator = 0.0;
|
||||
double denominator = c2 - c3 * encoded_power;
|
||||
double luminance = denominator > 0.0
|
||||
? pow(numerator / denominator, 1.0 / m1)
|
||||
: 0.0;
|
||||
if (luminance < 0.0) luminance = 0.0;
|
||||
if (luminance > 1.0) luminance = 1.0;
|
||||
fluxer_pq_lut[index] = (float)luminance;
|
||||
}
|
||||
const double a = 0.17883277;
|
||||
const double b = 0.28466892;
|
||||
const double c = 0.55991073;
|
||||
for (int index = 0; index < FLUXER_HDR_HLG_LUT_SIZE; index++) {
|
||||
double encoded = (double)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
double scene = encoded <= 0.5
|
||||
? (encoded * encoded) / 3.0
|
||||
: (exp((encoded - c) / a) + b) / 12.0;
|
||||
if (scene < 0.0) scene = 0.0;
|
||||
if (scene > 1.0) scene = 1.0;
|
||||
fluxer_hlg_lut[index] = (float)scene;
|
||||
double normalized = (double)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
fluxer_hlg_ootf_scale_lut[index] = normalized > 0.0
|
||||
? (float)pow(normalized, 0.2)
|
||||
: 0.0f;
|
||||
}
|
||||
fluxer_pq_sdr_target_perceptual =
|
||||
fluxer_pq_oetf(FLUXER_PQ_SDR_TARGET_NORM);
|
||||
fluxer_hlg_source_peak_perceptual =
|
||||
fluxer_pq_oetf(FLUXER_HLG_REFERENCE_PEAK_NORM);
|
||||
fluxer_hlg_sdr_target_perceptual =
|
||||
fluxer_pq_sdr_target_perceptual /
|
||||
fluxer_hlg_source_peak_perceptual;
|
||||
for (int index = 0; index < FLUXER_HDR_PQ_LUT_SIZE; index++) {
|
||||
fluxer_pq_tone_scale_lut[index] = fluxer_hdr_tone_scale(
|
||||
fluxer_pq_lut[index], FLUXER_PQ_SDR_TARGET_NORM,
|
||||
1.0f,
|
||||
fluxer_pq_sdr_target_perceptual);
|
||||
}
|
||||
for (int index = 0; index < FLUXER_HDR_HLG_LUT_SIZE; index++) {
|
||||
float maximum = (float)index / (FLUXER_HDR_HLG_LUT_SIZE - 1);
|
||||
float absolute_maximum =
|
||||
maximum * FLUXER_HLG_REFERENCE_PEAK_NORM;
|
||||
fluxer_hlg_tone_scale_lut[index] =
|
||||
FLUXER_HLG_REFERENCE_PEAK_NORM * fluxer_hdr_tone_scale(
|
||||
absolute_maximum, FLUXER_PQ_SDR_TARGET_NORM,
|
||||
fluxer_hlg_source_peak_perceptual,
|
||||
fluxer_hlg_sdr_target_perceptual);
|
||||
}
|
||||
for (int index = 0; index < FLUXER_HDR_SRGB_LUT_SIZE; index++) {
|
||||
float linear = (float)index / (FLUXER_HDR_SRGB_LUT_SIZE - 1);
|
||||
fluxer_srgb_lut[index] = fluxer_quantize8(
|
||||
fluxer_srgb_oetf(linear));
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t fluxer_hdr_lut_index(uint16_t code, int bit_depth) {
|
||||
assert(bit_depth == 10 || bit_depth == 12);
|
||||
if (bit_depth == 12) return code & 0x0fffu;
|
||||
uint16_t code10 = code & 0x03ffu;
|
||||
return (uint16_t)((code10 << 2) | (code10 >> 8));
|
||||
}
|
||||
|
||||
static inline uint16_t fluxer_unit_lut_index(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return FLUXER_HDR_HLG_LUT_SIZE - 1;
|
||||
return (uint16_t)(
|
||||
value * (FLUXER_HDR_HLG_LUT_SIZE - 1) + 0.5f);
|
||||
}
|
||||
|
||||
static inline uint16_t fluxer_heif_read_le16(const uint8_t *value) {
|
||||
return (uint16_t)((uint16_t)value[0] | ((uint16_t)value[1] << 8));
|
||||
}
|
||||
|
||||
static inline float fluxer_bt2390_eetf_perceptual(
|
||||
float encoded,
|
||||
float max_luminance
|
||||
) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (max_luminance >= 1.0f) {
|
||||
return encoded > 1.0f ? 1.0f : encoded;
|
||||
}
|
||||
float knee = 1.5f * max_luminance - 0.5f;
|
||||
if (encoded < knee) return encoded;
|
||||
if (encoded >= 1.0f) return max_luminance;
|
||||
float position = (encoded - knee) / (1.0f - knee);
|
||||
float squared = position * position;
|
||||
float cubed = squared * position;
|
||||
float start_basis = 2.0f * cubed - 3.0f * squared + 1.0f;
|
||||
float tangent_basis = cubed - 2.0f * squared + position;
|
||||
float end_basis = -2.0f * cubed + 3.0f * squared;
|
||||
float mapped = start_basis * knee + tangent_basis * (1.0f - knee) +
|
||||
end_basis * max_luminance;
|
||||
if (mapped > max_luminance) mapped = max_luminance;
|
||||
if (mapped < 0.0f) mapped = 0.0f;
|
||||
return mapped;
|
||||
}
|
||||
|
||||
static inline float fluxer_pq_oetf(float luminance) {
|
||||
if (luminance <= 0.0f) return 0.0f;
|
||||
if (luminance >= 1.0f) luminance = 1.0f;
|
||||
const float m1 = 0.1593017578125f;
|
||||
const float m2 = 78.84375f;
|
||||
const float c1 = 0.8359375f;
|
||||
const float c2 = 18.8515625f;
|
||||
const float c3 = 18.6875f;
|
||||
float power = powf(luminance, m1);
|
||||
return powf((c1 + c2 * power) / (1.0f + c3 * power), m2);
|
||||
}
|
||||
|
||||
static inline float fluxer_srgb_oetf(float value) {
|
||||
if (value <= 0.0f) return 0.0f;
|
||||
if (value >= 1.0f) return 1.0f;
|
||||
if (value <= 0.0031308f) return 12.92f * value;
|
||||
return 1.055f * powf(value, 1.0f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
static inline uint8_t fluxer_quantize8(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return 255;
|
||||
int quantized = (int)(value * 255.0f + 0.5f);
|
||||
if (quantized < 0) return 0;
|
||||
if (quantized > 255) return 255;
|
||||
return (uint8_t)quantized;
|
||||
}
|
||||
|
||||
static inline uint8_t fluxer_srgb_lut_quantize(float value) {
|
||||
if (value <= 0.0f) return 0;
|
||||
if (value >= 1.0f) return 255;
|
||||
size_t index = (size_t)(
|
||||
value * (FLUXER_HDR_SRGB_LUT_SIZE - 1) + 0.5f);
|
||||
assert(index < FLUXER_HDR_SRGB_LUT_SIZE);
|
||||
return fluxer_srgb_lut[index];
|
||||
}
|
||||
|
||||
static inline void fluxer_bt2020_to_bt709_linear(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
*out_red = 1.6605f * red - 0.5876f * green - 0.0728f * blue;
|
||||
*out_green = -0.1246f * red + 1.1329f * green - 0.0083f * blue;
|
||||
*out_blue = -0.0182f * red - 0.1006f * green + 1.1187f * blue;
|
||||
}
|
||||
|
||||
static inline void fluxer_display_p3_to_srgb_linear(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
*out_red = 1.2249401f * red - 0.2249404f * green;
|
||||
*out_green = -0.0420569f * red + 1.0420571f * green;
|
||||
*out_blue = -0.0196376f * red - 0.0786361f * green + 1.0982735f * blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_srgb(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded <= 0.04045f) return encoded / 12.92f;
|
||||
return powf((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_bt709(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded < 0.081f) return encoded / 4.5f;
|
||||
return powf((encoded + 0.099f) / 1.099f, 1.0f / 0.45f);
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_bt2020_12(float encoded) {
|
||||
if (encoded <= 0.0f) return 0.0f;
|
||||
if (encoded >= 1.0f) return 1.0f;
|
||||
if (encoded < 0.08145f) return encoded / 4.5f;
|
||||
return powf((encoded + 0.0993f) / 1.0993f, 1.0f / 0.45f);
|
||||
}
|
||||
|
||||
static inline void fluxer_heif_convert_gamut_linear(
|
||||
int gamut,
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float *out_red,
|
||||
float *out_green,
|
||||
float *out_blue
|
||||
) {
|
||||
if (gamut == FLUXER_HEIF_GAMUT_BT2020) {
|
||||
fluxer_bt2020_to_bt709_linear(
|
||||
red, green, blue, out_red, out_green, out_blue);
|
||||
return;
|
||||
}
|
||||
if (gamut == FLUXER_HEIF_GAMUT_DISPLAY_P3) {
|
||||
fluxer_display_p3_to_srgb_linear(
|
||||
red, green, blue, out_red, out_green, out_blue);
|
||||
return;
|
||||
}
|
||||
assert(gamut == FLUXER_HEIF_GAMUT_SRGB);
|
||||
*out_red = red;
|
||||
*out_green = green;
|
||||
*out_blue = blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_heif_linear_luma(
|
||||
int gamut,
|
||||
float red,
|
||||
float green,
|
||||
float blue
|
||||
) {
|
||||
if (gamut == FLUXER_HEIF_GAMUT_BT2020) {
|
||||
return 0.2627f * red + 0.6780f * green + 0.0593f * blue;
|
||||
}
|
||||
if (gamut == FLUXER_HEIF_GAMUT_DISPLAY_P3) {
|
||||
return 0.2289746f * red + 0.6917385f * green + 0.0792869f * blue;
|
||||
}
|
||||
assert(gamut == FLUXER_HEIF_GAMUT_SRGB);
|
||||
return 0.2126f * red + 0.7152f * green + 0.0722f * blue;
|
||||
}
|
||||
|
||||
static inline float fluxer_inverse_pq(float encoded) {
|
||||
const float m1 = 0.1593017578125f;
|
||||
const float m2 = 78.84375f;
|
||||
const float c1 = 0.8359375f;
|
||||
const float c2 = 18.8515625f;
|
||||
const float c3 = 18.6875f;
|
||||
float power = powf(encoded, 1.0f / m2);
|
||||
float numerator = power - c1;
|
||||
if (numerator < 0.0f) numerator = 0.0f;
|
||||
float denominator = c2 - c3 * power;
|
||||
if (denominator <= 0.0f) return 0.0f;
|
||||
float luminance = powf(numerator / denominator, 1.0f / m1);
|
||||
return luminance < 0.0f ? 0.0f : luminance;
|
||||
}
|
||||
|
||||
static inline float fluxer_hdr_tone_scale(
|
||||
float maximum,
|
||||
float target_normalized,
|
||||
float source_peak_perceptual,
|
||||
float target_perceptual
|
||||
) {
|
||||
if (maximum <= 0.0f) return 0.0f;
|
||||
assert(source_peak_perceptual > 0.0f);
|
||||
assert(source_peak_perceptual <= 1.0f);
|
||||
float perceptual = fluxer_pq_oetf(maximum) /
|
||||
source_peak_perceptual;
|
||||
float mapped_perceptual = fluxer_bt2390_eetf_perceptual(
|
||||
perceptual, target_perceptual);
|
||||
float mapped = fluxer_inverse_pq(
|
||||
mapped_perceptual * source_peak_perceptual);
|
||||
return (mapped / maximum) / target_normalized;
|
||||
}
|
||||
|
||||
static inline void fluxer_hdr_pipeline_pixel(
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float scale,
|
||||
int gamut,
|
||||
uint8_t *output
|
||||
) {
|
||||
float display_red = red * scale;
|
||||
float display_green = green * scale;
|
||||
float display_blue = blue * scale;
|
||||
if (display_red < 0.0f) display_red = 0.0f;
|
||||
if (display_green < 0.0f) display_green = 0.0f;
|
||||
if (display_blue < 0.0f) display_blue = 0.0f;
|
||||
if (display_red > 1.0f) display_red = 1.0f;
|
||||
if (display_green > 1.0f) display_green = 1.0f;
|
||||
if (display_blue > 1.0f) display_blue = 1.0f;
|
||||
float linear_red = display_red;
|
||||
float linear_green = display_green;
|
||||
float linear_blue = display_blue;
|
||||
fluxer_heif_convert_gamut_linear(
|
||||
gamut, display_red, display_green, display_blue,
|
||||
&linear_red, &linear_green, &linear_blue);
|
||||
if (linear_red < 0.0f) linear_red = 0.0f;
|
||||
if (linear_green < 0.0f) linear_green = 0.0f;
|
||||
if (linear_blue < 0.0f) linear_blue = 0.0f;
|
||||
if (linear_red > 1.0f) linear_red = 1.0f;
|
||||
if (linear_green > 1.0f) linear_green = 1.0f;
|
||||
if (linear_blue > 1.0f) linear_blue = 1.0f;
|
||||
output[0] = fluxer_srgb_lut_quantize(linear_red);
|
||||
output[1] = fluxer_srgb_lut_quantize(linear_green);
|
||||
output[2] = fluxer_srgb_lut_quantize(linear_blue);
|
||||
}
|
||||
|
||||
static unsigned char fluxer_ascii_lower(unsigned char value) {
|
||||
if (value >= 'A' && value <= 'Z') {
|
||||
return (unsigned char)(value + ('a' - 'A'));
|
||||
@@ -625,13 +294,35 @@ static int fluxer_heif_nclx_gamut(int primaries, int *out_gamut) {
|
||||
assert(out_gamut != NULL);
|
||||
switch (primaries) {
|
||||
case heif_color_primaries_ITU_R_BT_709_5:
|
||||
*out_gamut = FLUXER_HEIF_GAMUT_SRGB;
|
||||
*out_gamut = FLUXER_HDR_GAMUT_SRGB;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
|
||||
*out_gamut = FLUXER_HEIF_GAMUT_BT2020;
|
||||
*out_gamut = FLUXER_HDR_GAMUT_BT2020;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
case heif_color_primaries_SMPTE_EG_432_1:
|
||||
*out_gamut = FLUXER_HEIF_GAMUT_DISPLAY_P3;
|
||||
*out_gamut = FLUXER_HDR_GAMUT_DISPLAY_P3;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
default:
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
static int fluxer_heif_nclx_transfer(int transfer, int *out_transfer) {
|
||||
assert(out_transfer != NULL);
|
||||
switch (transfer) {
|
||||
case heif_transfer_characteristic_IEC_61966_2_1:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_SRGB;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
case heif_transfer_characteristic_ITU_R_BT_709_5:
|
||||
case heif_transfer_characteristic_ITU_R_BT_601_6:
|
||||
case heif_transfer_characteristic_ITU_R_BT_2020_2_10bit:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_BT709;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
case heif_transfer_characteristic_ITU_R_BT_2020_2_12bit:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_BT2020_12;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
case heif_transfer_characteristic_linear:
|
||||
*out_transfer = FLUXER_HDR_TRANSFER_LINEAR;
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
default:
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
@@ -647,60 +338,15 @@ static int fluxer_heif_apply_sdr_nclx(
|
||||
) {
|
||||
assert(destination != NULL);
|
||||
assert(profile != NULL);
|
||||
int gamut = FLUXER_HEIF_GAMUT_SRGB;
|
||||
int gamut = FLUXER_HDR_GAMUT_SRGB;
|
||||
int status = fluxer_heif_nclx_gamut(profile->primaries, &gamut);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
int transfer = profile->transfer;
|
||||
int is_srgb = transfer == heif_transfer_characteristic_IEC_61966_2_1;
|
||||
int is_bt709 = transfer == heif_transfer_characteristic_ITU_R_BT_709_5 ||
|
||||
transfer == heif_transfer_characteristic_ITU_R_BT_601_6 ||
|
||||
transfer == heif_transfer_characteristic_ITU_R_BT_2020_2_10bit;
|
||||
int is_bt2020_12 =
|
||||
transfer == heif_transfer_characteristic_ITU_R_BT_2020_2_12bit;
|
||||
int is_linear = transfer == heif_transfer_characteristic_linear;
|
||||
if (!is_srgb && !is_bt709 && !is_bt2020_12 && !is_linear) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
if (is_srgb && gamut == FLUXER_HEIF_GAMUT_SRGB) {
|
||||
return FLUXER_NATIVE_STATUS_OK;
|
||||
}
|
||||
size_t row_bytes = (size_t)width * 4u;
|
||||
for (int row = 0; row < height; row++) {
|
||||
if (row % FLUXER_HEIF_DEADLINE_ROWS == 0) {
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
}
|
||||
uint8_t *row_data = destination + (size_t)row * row_bytes;
|
||||
for (int column = 0; column < width; column++) {
|
||||
uint8_t *pixel = row_data + (size_t)column * 4u;
|
||||
float red = (float)pixel[0] / 255.0f;
|
||||
float green = (float)pixel[1] / 255.0f;
|
||||
float blue = (float)pixel[2] / 255.0f;
|
||||
if (is_srgb) {
|
||||
red = fluxer_inverse_srgb(red);
|
||||
green = fluxer_inverse_srgb(green);
|
||||
blue = fluxer_inverse_srgb(blue);
|
||||
} else if (is_bt709) {
|
||||
red = fluxer_inverse_bt709(red);
|
||||
green = fluxer_inverse_bt709(green);
|
||||
blue = fluxer_inverse_bt709(blue);
|
||||
} else if (is_bt2020_12) {
|
||||
red = fluxer_inverse_bt2020_12(red);
|
||||
green = fluxer_inverse_bt2020_12(green);
|
||||
blue = fluxer_inverse_bt2020_12(blue);
|
||||
}
|
||||
float srgb_red = 0.0f;
|
||||
float srgb_green = 0.0f;
|
||||
float srgb_blue = 0.0f;
|
||||
fluxer_heif_convert_gamut_linear(
|
||||
gamut, red, green, blue,
|
||||
&srgb_red, &srgb_green, &srgb_blue);
|
||||
pixel[0] = fluxer_quantize8(fluxer_srgb_oetf(srgb_red));
|
||||
pixel[1] = fluxer_quantize8(fluxer_srgb_oetf(srgb_green));
|
||||
pixel[2] = fluxer_quantize8(fluxer_srgb_oetf(srgb_blue));
|
||||
}
|
||||
}
|
||||
return fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
int transfer = FLUXER_HDR_TRANSFER_SRGB;
|
||||
status = fluxer_heif_nclx_transfer(profile->transfer, &transfer);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
return fluxer_hdr_apply_sdr_gamut(
|
||||
destination, width, height, gamut, transfer,
|
||||
FLUXER_HEIF_DEADLINE_ROWS, deadline_monotonic_ms);
|
||||
}
|
||||
|
||||
static int fluxer_heif_cancel_decoding(void *opaque) {
|
||||
@@ -860,67 +506,6 @@ static int fluxer_heif_decode_sdr(
|
||||
return status;
|
||||
}
|
||||
|
||||
struct fluxer_heif_hdr_transform {
|
||||
int bit_depth;
|
||||
int mask;
|
||||
int gamut;
|
||||
int is_hlg;
|
||||
const float *linear_lut;
|
||||
const float *tone_scale_lut;
|
||||
};
|
||||
|
||||
static void fluxer_heif_transform_hdr_row(
|
||||
const uint8_t *source,
|
||||
uint8_t *destination,
|
||||
int width,
|
||||
const struct fluxer_heif_hdr_transform *transform
|
||||
) {
|
||||
assert(source != NULL);
|
||||
assert(destination != NULL);
|
||||
assert(transform != NULL);
|
||||
for (int column = 0; column < width; column++) {
|
||||
const uint8_t *source_pixel = source + (size_t)column * 8u;
|
||||
uint16_t red_code =
|
||||
fluxer_heif_read_le16(source_pixel) & transform->mask;
|
||||
uint16_t green_code =
|
||||
fluxer_heif_read_le16(source_pixel + 2) & transform->mask;
|
||||
uint16_t blue_code =
|
||||
fluxer_heif_read_le16(source_pixel + 4) & transform->mask;
|
||||
uint16_t red_index = fluxer_hdr_lut_index(
|
||||
red_code, transform->bit_depth);
|
||||
uint16_t green_index = fluxer_hdr_lut_index(
|
||||
green_code, transform->bit_depth);
|
||||
uint16_t blue_index = fluxer_hdr_lut_index(
|
||||
blue_code, transform->bit_depth);
|
||||
float red = transform->linear_lut[red_index];
|
||||
float green = transform->linear_lut[green_index];
|
||||
float blue = transform->linear_lut[blue_index];
|
||||
uint16_t maximum_index = red_index;
|
||||
if (green_index > maximum_index) maximum_index = green_index;
|
||||
if (blue_index > maximum_index) maximum_index = blue_index;
|
||||
if (transform->is_hlg) {
|
||||
float luma = fluxer_heif_linear_luma(
|
||||
transform->gamut, red, green, blue);
|
||||
float ootf_scale =
|
||||
fluxer_hlg_ootf_scale_lut[fluxer_unit_lut_index(luma)];
|
||||
red *= ootf_scale;
|
||||
green *= ootf_scale;
|
||||
blue *= ootf_scale;
|
||||
float maximum = fmaxf(red, fmaxf(green, blue));
|
||||
maximum_index = fluxer_unit_lut_index(maximum);
|
||||
}
|
||||
float scale = transform->tone_scale_lut[maximum_index];
|
||||
uint8_t *destination_pixel = destination + (size_t)column * 4u;
|
||||
fluxer_hdr_pipeline_pixel(
|
||||
red, green, blue, scale, transform->gamut,
|
||||
destination_pixel);
|
||||
uint16_t alpha =
|
||||
fluxer_heif_read_le16(source_pixel + 6) & transform->mask;
|
||||
destination_pixel[3] = (uint8_t)(
|
||||
(alpha * 255 + (transform->mask >> 1)) / transform->mask);
|
||||
}
|
||||
}
|
||||
|
||||
static int fluxer_heif_decode_hdr(
|
||||
struct heif_image_handle *handle,
|
||||
uint8_t *destination,
|
||||
@@ -931,11 +516,6 @@ static int fluxer_heif_decode_hdr(
|
||||
) {
|
||||
int status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
if (pthread_once(&fluxer_hdr_lut_once, fluxer_init_hdr_luts) != 0) {
|
||||
return FLUXER_NATIVE_STATUS_CODEC_FAILURE;
|
||||
}
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) return status;
|
||||
int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
|
||||
if (bit_depth != 10 && bit_depth != 12) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
@@ -943,10 +523,19 @@ static int fluxer_heif_decode_hdr(
|
||||
if (!profile->nclx_present) {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
int gamut = FLUXER_HEIF_GAMUT_SRGB;
|
||||
int transfer;
|
||||
if (profile->transfer == heif_transfer_characteristic_ITU_R_BT_2100_0_PQ) {
|
||||
transfer = FLUXER_HDR_TRANSFER_PQ;
|
||||
} else if (profile->transfer ==
|
||||
heif_transfer_characteristic_ITU_R_BT_2100_0_HLG) {
|
||||
transfer = FLUXER_HDR_TRANSFER_HLG;
|
||||
} else {
|
||||
return FLUXER_NATIVE_STATUS_UNSUPPORTED;
|
||||
}
|
||||
int gamut = FLUXER_HDR_GAMUT_SRGB;
|
||||
if (fluxer_heif_nclx_gamut(profile->primaries, &gamut) !=
|
||||
FLUXER_NATIVE_STATUS_OK) {
|
||||
gamut = FLUXER_HEIF_GAMUT_SRGB;
|
||||
gamut = FLUXER_HDR_GAMUT_SRGB;
|
||||
}
|
||||
if ((size_t)width > SIZE_MAX / 8u) {
|
||||
return FLUXER_NATIVE_STATUS_INVALID_DIMENSIONS;
|
||||
@@ -961,37 +550,11 @@ static int fluxer_heif_decode_hdr(
|
||||
status = fluxer_heif_interleaved_plane(
|
||||
image, width, height, heif_chroma_interleaved_RRGGBBAA_LE,
|
||||
64, bit_depth, (size_t)width * 8u, &plane, &stride);
|
||||
struct fluxer_heif_hdr_transform transform = {
|
||||
.bit_depth = bit_depth,
|
||||
.mask = (1 << bit_depth) - 1,
|
||||
.gamut = gamut,
|
||||
.is_hlg =
|
||||
profile->transfer == heif_transfer_characteristic_ITU_R_BT_2100_0_HLG,
|
||||
.linear_lut =
|
||||
profile->transfer == heif_transfer_characteristic_ITU_R_BT_2100_0_PQ
|
||||
? fluxer_pq_lut
|
||||
: fluxer_hlg_lut,
|
||||
.tone_scale_lut =
|
||||
profile->transfer == heif_transfer_characteristic_ITU_R_BT_2100_0_PQ
|
||||
? fluxer_pq_tone_scale_lut
|
||||
: fluxer_hlg_tone_scale_lut,
|
||||
};
|
||||
if (status == FLUXER_NATIVE_STATUS_OK) {
|
||||
size_t destination_stride = (size_t)width * 4u;
|
||||
for (int row = 0; row < height; row++) {
|
||||
if (row % FLUXER_HEIF_DEADLINE_ROWS == 0) {
|
||||
status = fluxer_native_deadline_status(
|
||||
deadline_monotonic_ms);
|
||||
if (status != FLUXER_NATIVE_STATUS_OK) break;
|
||||
}
|
||||
fluxer_heif_transform_hdr_row(
|
||||
plane + (size_t)row * (size_t)stride,
|
||||
destination + (size_t)row * destination_stride,
|
||||
width, &transform);
|
||||
}
|
||||
if (status == FLUXER_NATIVE_STATUS_OK) {
|
||||
status = fluxer_native_deadline_status(deadline_monotonic_ms);
|
||||
}
|
||||
status = fluxer_hdr_tone_map_rgba16(
|
||||
plane, (size_t)stride, destination, (size_t)width * 4u,
|
||||
width, height, bit_depth, gamut, transfer,
|
||||
FLUXER_HEIF_DEADLINE_ROWS, deadline_monotonic_ms);
|
||||
}
|
||||
heif_image_release(image);
|
||||
return status;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define _DARWIN_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "hdr_color.h"
|
||||
#include "vips_shim.h"
|
||||
#include "webp_animation.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user