diff --git a/fluxer_media_proxy/build.rs b/fluxer_media_proxy/build.rs index c6dc03c07..fcf1e42e8 100644 --- a/fluxer_media_proxy/build.rs +++ b/fluxer_media_proxy/build.rs @@ -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", diff --git a/fluxer_media_proxy/src/component_tests.rs b/fluxer_media_proxy/src/component_tests.rs index e02370dcf..69fc95e58 100644 --- a/fluxer_media_proxy/src/component_tests.rs +++ b/fluxer_media_proxy/src/component_tests.rs @@ -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); diff --git a/fluxer_media_proxy/src/media_process/tests/video.rs b/fluxer_media_proxy/src/media_process/tests/video.rs index e6683fda8..a84f87dfa 100644 --- a/fluxer_media_proxy/src/media_process/tests/video.rs +++ b/fluxer_media_proxy/src/media_process/tests/video.rs @@ -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> { + 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:?}" ); } diff --git a/fluxer_media_proxy/src/native_shim/av_frame_rgba.c b/fluxer_media_proxy/src/native_shim/av_frame_rgba.c index 23c705f11..caf28b846 100644 --- a/fluxer_media_proxy/src/native_shim/av_frame_rgba.c +++ b/fluxer_media_proxy/src/native_shim/av_frame_rgba.c @@ -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); } diff --git a/fluxer_media_proxy/src/native_shim/hdr_color.c b/fluxer_media_proxy/src/native_shim/hdr_color.c new file mode 100644 index 000000000..8b38ae25c --- /dev/null +++ b/fluxer_media_proxy/src/native_shim/hdr_color.c @@ -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); +} diff --git a/fluxer_media_proxy/src/native_shim/hdr_color.h b/fluxer_media_proxy/src/native_shim/hdr_color.h new file mode 100644 index 000000000..3ddf3c7fa --- /dev/null +++ b/fluxer_media_proxy/src/native_shim/hdr_color.h @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +#pragma once + +#include "vips_shim.h" + +#include +#include +#include +#include + +#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); +} diff --git a/fluxer_media_proxy/src/native_shim/heif_pixels.c b/fluxer_media_proxy/src/native_shim/heif_pixels.c index 606a530d0..cd528eee5 100644 --- a/fluxer_media_proxy/src/native_shim/heif_pixels.c +++ b/fluxer_media_proxy/src/native_shim/heif_pixels.c @@ -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; diff --git a/fluxer_media_proxy/src/native_shim/native_shim_internal.h b/fluxer_media_proxy/src/native_shim/native_shim_internal.h index 32055eda1..53d7626fe 100644 --- a/fluxer_media_proxy/src/native_shim/native_shim_internal.h +++ b/fluxer_media_proxy/src/native_shim/native_shim_internal.h @@ -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"