[fix] prevent green tint during NV12 scaling

This commit is contained in:
dijunkun
2026-08-25 03:37:02 +08:00
parent 0d9d385d10
commit 1690fc92a3
5 changed files with 94 additions and 5 deletions
+62
View File
@@ -0,0 +1,62 @@
#include "nv12_scaler.h"
#include <cstddef>
#include <limits>
#include "libyuv/planar_functions.h"
namespace crossdesk {
int ScaleNv12ViaI420(const uint8_t* src_y, int src_stride_y,
const uint8_t* src_uv, int src_stride_uv, int src_width,
int src_height, uint8_t* dst_y, int dst_stride_y,
uint8_t* dst_uv, int dst_stride_uv, int dst_width,
int dst_height, libyuv::FilterMode filtering,
std::vector<uint8_t>* scratch_buffer) {
if (!src_y || !src_uv || !dst_y || !dst_uv || !scratch_buffer ||
src_width <= 0 || src_height <= 0 || dst_width <= 0 || dst_height <= 0 ||
(src_width & 1) != 0 || (src_height & 1) != 0 || (dst_width & 1) != 0 ||
(dst_height & 1) != 0) {
return -1;
}
const int src_chroma_width = src_width / 2;
const int src_chroma_height = src_height / 2;
const int dst_chroma_width = dst_width / 2;
const int dst_chroma_height = dst_height / 2;
const size_t src_chroma_size =
static_cast<size_t>(src_chroma_width) * src_chroma_height;
const size_t dst_chroma_size =
static_cast<size_t>(dst_chroma_width) * dst_chroma_height;
const size_t max_size = std::numeric_limits<size_t>::max();
if (src_chroma_size > max_size / 2 || dst_chroma_size > max_size / 2) {
return -1;
}
const size_t src_planes_size = 2 * src_chroma_size;
const size_t dst_planes_size = 2 * dst_chroma_size;
if (src_planes_size > max_size - dst_planes_size) {
return -1;
}
scratch_buffer->resize(src_planes_size + dst_planes_size);
uint8_t* src_u = scratch_buffer->data();
uint8_t* src_v = src_u + src_chroma_size;
uint8_t* dst_u = src_v + src_chroma_size;
uint8_t* dst_v = dst_u + dst_chroma_size;
libyuv::SplitUVPlane(src_uv, src_stride_uv, src_u, src_chroma_width, src_v,
src_chroma_width, src_chroma_width, src_chroma_height);
const int result = libyuv::I420Scale(
src_y, src_stride_y, src_u, src_chroma_width, src_v, src_chroma_width,
src_width, src_height, dst_y, dst_stride_y, dst_u, dst_chroma_width,
dst_v, dst_chroma_width, dst_width, dst_height, filtering);
if (result != 0) {
return result;
}
libyuv::MergeUVPlane(dst_u, dst_chroma_width, dst_v, dst_chroma_width, dst_uv,
dst_stride_uv, dst_chroma_width, dst_chroma_height);
return 0;
}
} // namespace crossdesk
+23
View File
@@ -0,0 +1,23 @@
#ifndef CROSSDESK_COMMON_NV12_SCALER_H_
#define CROSSDESK_COMMON_NV12_SCALER_H_
#include <cstdint>
#include <vector>
#include "libyuv/scale.h"
namespace crossdesk {
// Scales NV12 by separating the interleaved chroma plane before filtering.
// This avoids the downward chroma rounding bias in libyuv::NV12Scale while
// preserving the requested filtering quality.
int ScaleNv12ViaI420(const uint8_t* src_y, int src_stride_y,
const uint8_t* src_uv, int src_stride_uv, int src_width,
int src_height, uint8_t* dst_y, int dst_stride_y,
uint8_t* dst_uv, int dst_stride_uv, int dst_width,
int dst_height, libyuv::FilterMode filtering,
std::vector<uint8_t>* scratch_buffer);
} // namespace crossdesk
#endif // CROSSDESK_COMMON_NV12_SCALER_H_
+7 -4
View File
@@ -29,6 +29,7 @@
#include "crossdesk_ui.h"
#include "fa_solid_900.h"
#include "localization.h"
#include "nv12_scaler.h"
#include "platform.h"
#if _WIN32
#include <windows.h>
@@ -866,6 +867,7 @@ struct GuiApplication::SlintUi {
std::make_shared<slint::VectorModel<slint::SharedString>>();
std::unordered_map<std::string, uint64_t> displayed_frame_sequence;
std::vector<uint8_t> scaled_video_frame;
std::vector<uint8_t> scaled_video_frame_scratch;
#if !defined(__APPLE__)
std::mutex video_gl_mutex;
std::vector<uint8_t> video_gl_conversion_frame;
@@ -2651,10 +2653,11 @@ void GuiApplication::SyncStreamVideoFrame() {
uint8_t* scaled_uv =
scaled_y +
static_cast<size_t>(render_size.width) * render_size.height;
if (libyuv::NV12Scale(y_plane, width, uv_plane, width, width, height,
scaled_y, render_size.width, scaled_uv,
render_size.width, render_size.width,
render_size.height, libyuv::kFilterBox) == 0) {
if (ScaleNv12ViaI420(
y_plane, width, uv_plane, width, width, height, scaled_y,
render_size.width, scaled_uv, render_size.width, render_size.width,
render_size.height, libyuv::kFilterBox,
&ui_->scaled_video_frame_scratch) == 0) {
y_plane = scaled_y;
uv_plane = scaled_uv;
output_width = render_size.width;
+1
View File
@@ -33,6 +33,7 @@ function setup_targets()
target("common")
set_kind("object")
add_deps("rd_log")
add_packages("libyuv")
add_files("src/common/*.cpp")
remove_files("src/common/rounded_corner_button.cpp")
if is_os("macosx") then