1*c8dee2aaSAndroid Build Coastguard Worker /* 2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2023 Google Inc. 3*c8dee2aaSAndroid Build Coastguard Worker * 4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be 5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file. 6*c8dee2aaSAndroid Build Coastguard Worker */ 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Worker #ifndef SkGainmapInfo_DEFINED 9*c8dee2aaSAndroid Build Coastguard Worker #define SkGainmapInfo_DEFINED 10*c8dee2aaSAndroid Build Coastguard Worker 11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h" 12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorSpace.h" 13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h" 14*c8dee2aaSAndroid Build Coastguard Worker class SkData; 15*c8dee2aaSAndroid Build Coastguard Worker 16*c8dee2aaSAndroid Build Coastguard Worker /** 17*c8dee2aaSAndroid Build Coastguard Worker * Gainmap rendering parameters. Suppose our display has HDR to SDR ratio of H and we wish to 18*c8dee2aaSAndroid Build Coastguard Worker * display an image with gainmap on this display. Let B be the pixel value from the base image 19*c8dee2aaSAndroid Build Coastguard Worker * in a color space that has the primaries of the base image and a linear transfer function. Let 20*c8dee2aaSAndroid Build Coastguard Worker * G be the pixel value from the gainmap. Let D be the output pixel in the same color space as B. 21*c8dee2aaSAndroid Build Coastguard Worker * The value of D is computed as follows: 22*c8dee2aaSAndroid Build Coastguard Worker * 23*c8dee2aaSAndroid Build Coastguard Worker * First, let W be a weight parameter determing how much the gainmap will be applied. 24*c8dee2aaSAndroid Build Coastguard Worker * W = clamp((log(H) - log(fDisplayRatioSdr)) / 25*c8dee2aaSAndroid Build Coastguard Worker * (log(fDisplayRatioHdr) - log(fDisplayRatioSdr), 0, 1) 26*c8dee2aaSAndroid Build Coastguard Worker * 27*c8dee2aaSAndroid Build Coastguard Worker * Next, let L be the gainmap value in log space. We compute this from the value G that was 28*c8dee2aaSAndroid Build Coastguard Worker * sampled from the texture as follows: 29*c8dee2aaSAndroid Build Coastguard Worker * L = mix(log(fGainmapRatioMin), log(fGainmapRatioMax), pow(G, fGainmapGamma)) 30*c8dee2aaSAndroid Build Coastguard Worker * 31*c8dee2aaSAndroid Build Coastguard Worker * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then 32*c8dee2aaSAndroid Build Coastguard Worker * compute: 33*c8dee2aaSAndroid Build Coastguard Worker * D = (B + fEpsilonSdr) * exp(L * W) - fEpsilonHdr 34*c8dee2aaSAndroid Build Coastguard Worker * If the base image is HDR then compute: 35*c8dee2aaSAndroid Build Coastguard Worker * D = (B + fEpsilonHdr) * exp(L * (W - 1)) - fEpsilonSdr 36*c8dee2aaSAndroid Build Coastguard Worker * 37*c8dee2aaSAndroid Build Coastguard Worker * In the above math, log() is a natural logarithm and exp() is natural exponentiation. Note, 38*c8dee2aaSAndroid Build Coastguard Worker * however, that the base used for the log() and exp() functions does not affect the results of 39*c8dee2aaSAndroid Build Coastguard Worker * the computation (it cancels out, as long as the same base is used throughout). 40*c8dee2aaSAndroid Build Coastguard Worker * 41*c8dee2aaSAndroid Build Coastguard Worker * This product includes Gain Map technology under license by Adobe. 42*c8dee2aaSAndroid Build Coastguard Worker */ 43*c8dee2aaSAndroid Build Coastguard Worker struct SkGainmapInfo { 44*c8dee2aaSAndroid Build Coastguard Worker /** 45*c8dee2aaSAndroid Build Coastguard Worker * Parameters for converting the gainmap from its image encoding to log space. These are 46*c8dee2aaSAndroid Build Coastguard Worker * specified per color channel. The alpha value is unused. 47*c8dee2aaSAndroid Build Coastguard Worker */ 48*c8dee2aaSAndroid Build Coastguard Worker SkColor4f fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0}; 49*c8dee2aaSAndroid Build Coastguard Worker SkColor4f fGainmapRatioMax = {2.f, 2.f, 2.f, 1.0}; 50*c8dee2aaSAndroid Build Coastguard Worker SkColor4f fGainmapGamma = {1.f, 1.f, 1.f, 1.f}; 51*c8dee2aaSAndroid Build Coastguard Worker 52*c8dee2aaSAndroid Build Coastguard Worker /** 53*c8dee2aaSAndroid Build Coastguard Worker * Parameters sometimes used in gainmap computation to avoid numerical instability. 54*c8dee2aaSAndroid Build Coastguard Worker */ 55*c8dee2aaSAndroid Build Coastguard Worker SkColor4f fEpsilonSdr = {0.f, 0.f, 0.f, 1.0}; 56*c8dee2aaSAndroid Build Coastguard Worker SkColor4f fEpsilonHdr = {0.f, 0.f, 0.f, 1.0}; 57*c8dee2aaSAndroid Build Coastguard Worker 58*c8dee2aaSAndroid Build Coastguard Worker /** 59*c8dee2aaSAndroid Build Coastguard Worker * If the output display's HDR to SDR ratio is less or equal than fDisplayRatioSdr then the SDR 60*c8dee2aaSAndroid Build Coastguard Worker * rendition is displayed. If the output display's HDR to SDR ratio is greater or equal than 61*c8dee2aaSAndroid Build Coastguard Worker * fDisplayRatioHdr then the HDR rendition is displayed. If the output display's HDR to SDR 62*c8dee2aaSAndroid Build Coastguard Worker * ratio is between these values then an interpolation between the two is displayed using the 63*c8dee2aaSAndroid Build Coastguard Worker * math above. 64*c8dee2aaSAndroid Build Coastguard Worker */ 65*c8dee2aaSAndroid Build Coastguard Worker float fDisplayRatioSdr = 1.f; 66*c8dee2aaSAndroid Build Coastguard Worker float fDisplayRatioHdr = 2.f; 67*c8dee2aaSAndroid Build Coastguard Worker 68*c8dee2aaSAndroid Build Coastguard Worker /** 69*c8dee2aaSAndroid Build Coastguard Worker * Whether the base image is the SDR image or the HDR image. 70*c8dee2aaSAndroid Build Coastguard Worker */ 71*c8dee2aaSAndroid Build Coastguard Worker enum class BaseImageType { 72*c8dee2aaSAndroid Build Coastguard Worker kSDR, 73*c8dee2aaSAndroid Build Coastguard Worker kHDR, 74*c8dee2aaSAndroid Build Coastguard Worker }; 75*c8dee2aaSAndroid Build Coastguard Worker BaseImageType fBaseImageType = BaseImageType::kSDR; 76*c8dee2aaSAndroid Build Coastguard Worker 77*c8dee2aaSAndroid Build Coastguard Worker /** 78*c8dee2aaSAndroid Build Coastguard Worker * The type of the gainmap image. If the type is kApple, then the gainmap image was originally 79*c8dee2aaSAndroid Build Coastguard Worker * encoded according to the specification at [0], and can be converted to the kDefault type by 80*c8dee2aaSAndroid Build Coastguard Worker * applying the transformation described at [1]. 81*c8dee2aaSAndroid Build Coastguard Worker * [0] https://developer.apple.com/documentation/appkit/images_and_pdf/ 82*c8dee2aaSAndroid Build Coastguard Worker * applying_apple_hdr_effect_to_your_photos 83*c8dee2aaSAndroid Build Coastguard Worker * [1] https://docs.google.com/document/d/1iUpYAThVV_FuDdeiO3t0vnlfoA1ryq0WfGS9FuydwKc 84*c8dee2aaSAndroid Build Coastguard Worker */ 85*c8dee2aaSAndroid Build Coastguard Worker enum class Type { 86*c8dee2aaSAndroid Build Coastguard Worker kDefault, 87*c8dee2aaSAndroid Build Coastguard Worker kApple, 88*c8dee2aaSAndroid Build Coastguard Worker }; 89*c8dee2aaSAndroid Build Coastguard Worker Type fType = Type::kDefault; 90*c8dee2aaSAndroid Build Coastguard Worker 91*c8dee2aaSAndroid Build Coastguard Worker /** 92*c8dee2aaSAndroid Build Coastguard Worker * If specified, color space to apply the gainmap in, otherwise the base image's color space 93*c8dee2aaSAndroid Build Coastguard Worker * is used. Only the color primaries are used, the transfer function is irrelevant. 94*c8dee2aaSAndroid Build Coastguard Worker */ 95*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkColorSpace> fGainmapMathColorSpace = nullptr; 96*c8dee2aaSAndroid Build Coastguard Worker 97*c8dee2aaSAndroid Build Coastguard Worker /** 98*c8dee2aaSAndroid Build Coastguard Worker * Return true if this can be encoded as an UltraHDR v1 image. 99*c8dee2aaSAndroid Build Coastguard Worker */ 100*c8dee2aaSAndroid Build Coastguard Worker bool isUltraHDRv1Compatible() const; 101*c8dee2aaSAndroid Build Coastguard Worker 102*c8dee2aaSAndroid Build Coastguard Worker /** 103*c8dee2aaSAndroid Build Coastguard Worker * If |data| contains an ISO 21496-1 version that is supported, return true. Otherwise return 104*c8dee2aaSAndroid Build Coastguard Worker * false. 105*c8dee2aaSAndroid Build Coastguard Worker */ 106*c8dee2aaSAndroid Build Coastguard Worker static bool ParseVersion(const SkData* data); 107*c8dee2aaSAndroid Build Coastguard Worker 108*c8dee2aaSAndroid Build Coastguard Worker /** 109*c8dee2aaSAndroid Build Coastguard Worker * If |data| constains ISO 21496-1 metadata then parse that metadata then use it to populate 110*c8dee2aaSAndroid Build Coastguard Worker * |info| and return true, otherwise return false. If |data| indicates that that the base image 111*c8dee2aaSAndroid Build Coastguard Worker * color space primaries should be used for gainmap application then set 112*c8dee2aaSAndroid Build Coastguard Worker * |fGainmapMathColorSpace| to nullptr, otherwise set |fGainmapMathColorSpace| to sRGB (the 113*c8dee2aaSAndroid Build Coastguard Worker * default, to be overwritten by the image decoder). 114*c8dee2aaSAndroid Build Coastguard Worker */ 115*c8dee2aaSAndroid Build Coastguard Worker static bool Parse(const SkData* data, SkGainmapInfo& info); 116*c8dee2aaSAndroid Build Coastguard Worker 117*c8dee2aaSAndroid Build Coastguard Worker /** 118*c8dee2aaSAndroid Build Coastguard Worker * Serialize an ISO 21496-1 version 0 blob containing only the version structure. 119*c8dee2aaSAndroid Build Coastguard Worker */ 120*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkData> SerializeVersion(); 121*c8dee2aaSAndroid Build Coastguard Worker 122*c8dee2aaSAndroid Build Coastguard Worker /** 123*c8dee2aaSAndroid Build Coastguard Worker * Serialize an ISO 21496-1 version 0 blob containing this' gainmap parameters. 124*c8dee2aaSAndroid Build Coastguard Worker */ 125*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> serialize() const; 126*c8dee2aaSAndroid Build Coastguard Worker 127*c8dee2aaSAndroid Build Coastguard Worker inline bool operator==(const SkGainmapInfo& other) const { 128*c8dee2aaSAndroid Build Coastguard Worker return fGainmapRatioMin == other.fGainmapRatioMin && 129*c8dee2aaSAndroid Build Coastguard Worker fGainmapRatioMax == other.fGainmapRatioMax && fGainmapGamma == other.fGainmapGamma && 130*c8dee2aaSAndroid Build Coastguard Worker fEpsilonSdr == other.fEpsilonSdr && fEpsilonHdr == other.fEpsilonHdr && 131*c8dee2aaSAndroid Build Coastguard Worker fDisplayRatioSdr == other.fDisplayRatioSdr && 132*c8dee2aaSAndroid Build Coastguard Worker fDisplayRatioHdr == other.fDisplayRatioHdr && 133*c8dee2aaSAndroid Build Coastguard Worker fBaseImageType == other.fBaseImageType && fType == other.fType && 134*c8dee2aaSAndroid Build Coastguard Worker SkColorSpace::Equals(fGainmapMathColorSpace.get(), 135*c8dee2aaSAndroid Build Coastguard Worker other.fGainmapMathColorSpace.get()); 136*c8dee2aaSAndroid Build Coastguard Worker } 137*c8dee2aaSAndroid Build Coastguard Worker inline bool operator!=(const SkGainmapInfo& other) const { return !(*this == other); } 138*c8dee2aaSAndroid Build Coastguard Worker }; 139*c8dee2aaSAndroid Build Coastguard Worker 140*c8dee2aaSAndroid Build Coastguard Worker #endif 141