1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "Tonemapper.h"
18 
19 #include <SkRuntimeEffect.h>
20 #include <log/log.h>
21 // libshaders only exists on Android devices
22 #ifdef __ANDROID__
23 #include <renderthread/CanvasContext.h>
24 #include <shaders/shaders.h>
25 #endif
26 
27 #include "utils/Color.h"
28 
29 namespace android::uirenderer {
30 
31 namespace {
32 
33 // custom tonemapping only exists on Android devices
34 #ifdef __ANDROID__
35 class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
36 public:
ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)37     explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
38             : SkRuntimeEffectBuilder(std::move(effect)) {}
39 
makeColorFilter()40     sk_sp<SkColorFilter> makeColorFilter() {
41         return this->effect()->makeColorFilter(this->uniforms());
42     }
43 };
44 
createLinearEffectColorFilter(const shaders::LinearEffect & linearEffect,float maxDisplayLuminance,float currentDisplayLuminanceNits,float maxLuminance)45 static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
46                                                           float maxDisplayLuminance,
47                                                           float currentDisplayLuminanceNits,
48                                                           float maxLuminance) {
49     auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
50     auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
51     if (!runtimeEffect) {
52         LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
53     }
54 
55     ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
56 
57     auto colorTransform = android::mat4();
58     const auto* context = renderthread::CanvasContext::getActiveContext();
59     if (context) {
60         const auto ratio = context->targetSdrHdrRatio();
61         if (ratio > 1.0f) {
62             colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
63         }
64     }
65 
66     const auto uniforms =
67             shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
68                                                currentDisplayLuminanceNits, maxLuminance);
69 
70     for (const auto& uniform : uniforms) {
71         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
72     }
73 
74     return effectBuilder.makeColorFilter();
75 }
76 
extractTransfer(ui::Dataspace dataspace)77 static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
78     return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
79 }
80 
isHdrDataspace(ui::Dataspace dataspace)81 static bool isHdrDataspace(ui::Dataspace dataspace) {
82     const auto transfer = extractTransfer(dataspace);
83 
84     return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
85 }
86 
getDataspace(const SkImageInfo & image)87 static ui::Dataspace getDataspace(const SkImageInfo& image) {
88     return static_cast<ui::Dataspace>(
89             ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
90 }
91 #endif
92 
93 }  // namespace
94 
95 // Given a source and destination image info, and the max content luminance, generate a tonemaping
96 // shader and tag it on the supplied paint.
tonemapPaint(const SkImageInfo & source,const SkImageInfo & destination,float maxLuminanceNits,SkPaint & paint)97 void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
98                   SkPaint& paint) {
99 // custom tonemapping only exists on Android devices
100 #ifdef __ANDROID__
101     const auto sourceDataspace = getDataspace(source);
102     const auto destinationDataspace = getDataspace(destination);
103 
104     if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
105         (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
106         const auto effect = shaders::LinearEffect{
107                 .inputDataspace = sourceDataspace,
108                 .outputDataspace = destinationDataspace,
109                 .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
110                 .type = shaders::LinearEffect::SkSLType::ColorFilter};
111         constexpr float kMaxDisplayBrightnessNits = 1000.f;
112         constexpr float kCurrentDisplayBrightnessNits = 500.f;
113         sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
114                 effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
115 
116         if (paint.getColorFilter()) {
117             paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
118         } else {
119             paint.setColorFilter(colorFilter);
120         }
121     }
122 #else
123     return;
124 #endif
125 }
126 
127 }  // namespace android::uirenderer
128