blob: 49c0934dff616e352e39181e3020ebef951d81a7 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gfx/color_conversion_sk_filter_cache.h"
#include <string>
#include "base/feature_list.h"
#include "base/logging.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorFilter.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/effects/SkRuntimeEffect.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "ui/gfx/color_transform.h"
namespace gfx {
namespace {
const base::Feature kToneMappingV2{"ToneMappingV2",
base::FEATURE_ENABLED_BY_DEFAULT};
// Additional YUV information to skia renderer to draw 9- and 10- bits color.
struct YUVInput {
float offset = 0.f;
float multiplier = 0.f;
};
} // namespace
ColorConversionSkFilterCache::ColorConversionSkFilterCache() = default;
ColorConversionSkFilterCache::~ColorConversionSkFilterCache() = default;
bool ColorConversionSkFilterCache::Key::Key::operator==(
const Key& other) const {
return src == other.src && dst == other.dst &&
sdr_max_luminance_nits == other.sdr_max_luminance_nits &&
dst_max_luminance_relative == other.dst_max_luminance_relative;
}
bool ColorConversionSkFilterCache::Key::operator!=(const Key& other) const {
return !(*this == other);
}
bool ColorConversionSkFilterCache::Key::operator<(const Key& other) const {
return std::tie(src, dst, sdr_max_luminance_nits,
dst_max_luminance_relative) <
std::tie(other.src, other.dst, other.sdr_max_luminance_nits,
other.dst_max_luminance_relative);
}
ColorConversionSkFilterCache::Key::Key(const gfx::ColorSpace& src,
const gfx::ColorSpace& dst,
float sdr_max_luminance_nits,
float dst_max_luminance_relative)
: src(src),
dst(dst),
sdr_max_luminance_nits(sdr_max_luminance_nits),
dst_max_luminance_relative(dst_max_luminance_relative) {}
sk_sp<SkColorFilter> ColorConversionSkFilterCache::Get(
const gfx::ColorSpace& src,
const gfx::ColorSpace& dst,
float resource_offset,
float resource_multiplier,
float sdr_max_luminance_nits,
float dst_max_luminance_relative) {
// Set unused parameters to bogus values, so that they do not result in
// different keys for the same conversion.
if (!src.IsToneMappedByDefault()) {
// If the source is not going to be tone mapped, then
// `dst_max_luminance_relative` will not be used, so set it to a nonsense
// value.
dst_max_luminance_relative = 0;
// If neither source nor destination will use `sdr_max_luminance_nits`, then
// set it to a nonsense value.
if (!dst.IsAffectedBySDRWhiteLevel() && !src.IsAffectedBySDRWhiteLevel()) {
sdr_max_luminance_nits = 0;
}
}
const Key key(src, dst, sdr_max_luminance_nits, dst_max_luminance_relative);
sk_sp<SkRuntimeEffect>& effect = cache_[key];
if (!effect) {
gfx::ColorTransform::Options options;
static const bool tone_mapping_v2_enabled =
base::FeatureList::IsEnabled(kToneMappingV2);
if (tone_mapping_v2_enabled)
options.tone_map_pq_and_hlg_to_dst = true;
else
options.tone_map_pq_and_hlg_to_sdr = dst_max_luminance_relative == 1.f;
options.sdr_max_luminance_nits = key.sdr_max_luminance_nits;
// TODO(https://crbug.com/1286076): Ensure that, when tone mapping using
// `dst_max_luminance_relative` is implemented, the gfx::ColorTransform's
// SkShaderSource not depend on that parameter (rather, that it be left
// as a uniform in the shader). If that is not the case, then it will need
// to be part of the key.
options.dst_max_luminance_relative = dst_max_luminance_relative;
std::unique_ptr<gfx::ColorTransform> transform =
gfx::ColorTransform::NewColorTransform(src, dst, options);
const char* hdr = R"(
uniform half offset;
uniform half multiplier;
half4 main(half4 color) {
// un-premultiply alpha
if (color.a > 0)
color.rgb /= color.a;
color.rgb -= offset;
color.rgb *= multiplier;
)";
const char* ftr = R"(
// premultiply alpha
color.rgb *= color.a;
return color;
}
)";
std::string shader = hdr + transform->GetSkShaderSource() + ftr;
auto result = SkRuntimeEffect::MakeForColorFilter(
SkString(shader.c_str(), shader.size()),
/*options=*/{});
DCHECK(result.effect) << std::endl
<< result.errorText.c_str() << "\n\nShader Source:\n"
<< shader;
effect = result.effect;
}
YUVInput input;
input.offset = resource_offset;
input.multiplier = resource_multiplier;
sk_sp<SkData> data = SkData::MakeWithCopy(&input, sizeof(input));
return effect->makeColorFilter(std::move(data));
}
sk_sp<SkImage> ColorConversionSkFilterCache::ConvertImage(
sk_sp<SkImage> image,
sk_sp<SkColorSpace> target_color_space,
float sdr_max_luminance_nits,
float dst_max_luminance_relative,
GrDirectContext* context) {
sk_sp<SkColorSpace> image_sk_color_space = image->refColorSpace();
if (!image_sk_color_space)
return image->makeColorSpace(target_color_space, context);
gfx::ColorSpace image_color_space(*image_sk_color_space);
switch (image_color_space.GetTransferID()) {
case ColorSpace::TransferID::PQ:
case ColorSpace::TransferID::HLG:
break;
default:
return image->makeColorSpace(target_color_space, context);
}
SkImageInfo image_info =
SkImageInfo::Make(image->dimensions(),
SkColorInfo(kRGBA_F16_SkColorType, kPremul_SkAlphaType,
image_sk_color_space));
sk_sp<SkSurface> surface;
if (context) {
// TODO(https://crbug.com/1286088): Consider adding mipmap support here.
surface =
SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info,
/*sampleCount=*/0, kTopLeft_GrSurfaceOrigin,
/*surfaceProps=*/nullptr,
/*shouldCreateWithMips=*/false);
// It is not guaranteed that kRGBA_F16_SkColorType is renderable. If we fail
// to create an SkSurface with that color type, fall back to
// kN32_SkColorType.
if (!surface) {
DLOG(ERROR) << "Falling back to tone mapped 8-bit surface.";
image_info = image_info.makeColorType(kN32_SkColorType);
surface = SkSurface::MakeRenderTarget(
context, SkBudgeted::kNo, image_info,
/*sampleCount=*/0, kTopLeft_GrSurfaceOrigin,
/*surfaceProps=*/nullptr,
/*shouldCreateWithMips=*/false);
}
} else {
surface = SkSurface::MakeRaster(image_info, image_info.minRowBytes(),
/*surfaceProps=*/nullptr);
}
if (!surface) {
DLOG(ERROR) << "Failed to create SkSurface color conversion.";
return nullptr;
}
sk_sp<SkColorFilter> filter =
Get(image_color_space, gfx::ColorSpace(*target_color_space),
/*resource_offset=*/0, /*resource_multiplier=*/1,
sdr_max_luminance_nits, dst_max_luminance_relative);
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
paint.setColorFilter(filter);
SkSamplingOptions sampling_options(SkFilterMode::kNearest);
surface->getCanvas()->drawImage(image,
/*x=*/0, /*y=*/0, sampling_options, &paint);
return surface->makeImageSnapshot()->reinterpretColorSpace(
target_color_space);
}
} // namespace gfx