1 /*
2  * Copyright 2024 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 #pragma once
17 #include <SkImage.h>
18 #include <SkRuntimeEffect.h>
19 #include <SkShader.h>
20 #include "../compat/SkiaGpuContext.h"
21 namespace android {
22 namespace renderengine {
23 namespace skia {
24 /**
25  * MouriMap is a fast, albeit not realtime, tonemapping algorithm optimized for near-exact
26  * preservation of SDR (or, equivalently, LDR) regions, while trying to do an acceptable job of
27  * preserving HDR detail.
28  *
29  * MouriMap is a local tonemapping algorithm, meaning that nearby pixels are taken into
30  * consideration when choosing a tonemapping curve.
31  *
32  * The algorithm conceptually is as follows:
33  * 1. Partition the image into 128x128 chunks, computing the log2(maximum luminance) in each chunk
34  *.    a. Maximum luminance is computed as max(R, G, B), where the R, G, B values are in linear
35  *.       luminance on a scale defined by the destination color gamut. Max(R, G, B) has been found
36  *.       to minimize difference in hue while restricting to typical LDR color volumes. See: Burke,
37  *.       Adam & Smith, Michael & Zink, Michael. 2020. Color Volume and Hue-preservation in HDR
38  *.       Tone Mapping. SMPTE Motion Imaging Journal.
39  *.    b. Each computed luminance is lower-bounded by 1.0 in Skia's color
40  *.       management, or 203 nits.
41  * 2. Blur the resulting chunks using a 5x5 gaussian kernel, to smooth out the local luminance map.
42  * 3. Now, for each pixel in the original image:
43  *     a. Upsample from the blurred chunks of luminance computed in (2). Call this luminance value
44  *.       L: an estimate of the maximum luminance of surrounding pixels.
45  *.    b. If the luminance is less than 1.0 (203 nits), then do not modify the pixel value of the
46  *.       original image.
47  *.    c. Otherwise,
48  *.       parameterize a tone-mapping curve using a method described by Chrome:
49  *.       https://docs.google.com/document/d/17T2ek1i2R7tXdfHCnM-i5n6__RoYe0JyMfKmTEjoGR8/.
50  *.        i. Compute a gain G = (1 + max(linear R, linear G, linear B) / (L * L))
51  *.           / (1 + max(linear R, linear G, linear B)). Note the similarity with the 1D curve
52  *.           described by Erik Reinhard, Michael Stark, Peter Shirley, and James Ferwerda. 2002.
53  *.           Photographic tone reproduction for digital images. ACM Trans. Graph.
54  *.       ii. Multiply G by the linear source colors to compute the final colors.
55  *
56  * Because it is a multi-renderpass algorithm requiring multiple off-screen textures, MouriMap is
57  * typically not suitable to be ran "frequently", at high refresh rates (e.g., 120hz). However,
58  * MouriMap is sufficiently fast enough for infrequent composition where preserving SDR detail is
59  * most important, such as for screenshots.
60  */
61 class MouriMap {
62 public:
63     MouriMap();
64     // Apply the MouriMap tonemmaping operator to the input.
65     // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger
66     // then 1.0 means that there is headroom above the SDR region.
67     sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float hdrSdrRatio);
68 
69 private:
70     sk_sp<SkImage> downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
71                              float hdrSdrRatio) const;
72     sk_sp<SkImage> blur(SkiaGpuContext* context, SkImage* input) const;
73     sk_sp<SkShader> tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio) const;
74     const sk_sp<SkRuntimeEffect> mCrosstalkAndChunk16x16;
75     const sk_sp<SkRuntimeEffect> mChunk8x8;
76     const sk_sp<SkRuntimeEffect> mBlur;
77     const sk_sp<SkRuntimeEffect> mTonemap;
78 };
79 } // namespace skia
80 } // namespace renderengine
81 } // namespace android