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 <shaders/shaders.h>
24 #endif
25 
26 #include "utils/Color.h"
27 
28 namespace android::uirenderer {
29 
30 namespace {
31 
32 // custom tonemapping only exists on Android devices
33 #ifdef __ANDROID__
34 class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
35 public:
ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)36     explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
37             : SkRuntimeEffectBuilder(std::move(effect)) {}
38 
makeColorFilter()39     sk_sp<SkColorFilter> makeColorFilter() {
40         return this->effect()->makeColorFilter(this->uniforms());
41     }
42 };
43 
createLinearEffectColorFilter(const shaders::LinearEffect & linearEffect,float maxDisplayLuminance,float currentDisplayLuminanceNits,float maxLuminance)44 static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
45                                                           float maxDisplayLuminance,
46                                                           float currentDisplayLuminanceNits,
47                                                           float maxLuminance) {
48     auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
49     auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
50     if (!runtimeEffect) {
51         LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
52     }
53 
54     ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
55 
56     const auto uniforms =
57             shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
58                                                currentDisplayLuminanceNits, maxLuminance);
59 
60     for (const auto& uniform : uniforms) {
61         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
62     }
63 
64     return effectBuilder.makeColorFilter();
65 }
66 
extractTransfer(ui::Dataspace dataspace)67 static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
68     return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
69 }
70 
isHdrDataspace(ui::Dataspace dataspace)71 static bool isHdrDataspace(ui::Dataspace dataspace) {
72     const auto transfer = extractTransfer(dataspace);
73 
74     return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
75 }
76 
getDataspace(const SkImageInfo & image)77 static ui::Dataspace getDataspace(const SkImageInfo& image) {
78     return static_cast<ui::Dataspace>(
79             ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
80 }
81 #endif
82 
83 }  // namespace
84 
85 // Given a source and destination image info, and the max content luminance, generate a tonemaping
86 // shader and tag it on the supplied paint.
tonemapPaint(const SkImageInfo & source,const SkImageInfo & destination,float maxLuminanceNits,SkPaint & paint)87 void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
88                   SkPaint& paint) {
89 // custom tonemapping only exists on Android devices
90 #ifdef __ANDROID__
91     const auto sourceDataspace = getDataspace(source);
92     const auto destinationDataspace = getDataspace(destination);
93 
94     if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
95         (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
96         const auto effect = shaders::LinearEffect{
97                 .inputDataspace = sourceDataspace,
98                 .outputDataspace = destinationDataspace,
99                 .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
100                 .type = shaders::LinearEffect::SkSLType::ColorFilter};
101         constexpr float kMaxDisplayBrightnessNits = 1000.f;
102         constexpr float kCurrentDisplayBrightnessNits = 500.f;
103         sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
104                 effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
105 
106         if (paint.getColorFilter()) {
107             paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
108         } else {
109             paint.setColorFilter(colorFilter);
110         }
111     }
112 #else
113     return;
114 #endif
115 }
116 
117 }  // namespace android::uirenderer
118