/* * Copyright 2023 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/private/SkGainmapShader.h" #include "include/core/SkColor.h" #include "include/core/SkColorFilter.h" #include "include/core/SkColorSpace.h" #include "include/core/SkImage.h" #include "include/core/SkMatrix.h" #include "include/core/SkShader.h" #include "include/core/SkString.h" #include "include/effects/SkRuntimeEffect.h" #include "include/private/SkGainmapInfo.h" #include "include/private/base/SkAssert.h" #include "src/core/SkColorFilterPriv.h" #include "src/core/SkImageInfoPriv.h" #include #include static constexpr char gGainmapSKSL[] = "uniform shader base;" "uniform shader gainmap;" "uniform half4 logRatioMin;" "uniform half4 logRatioMax;" "uniform half4 gainmapGamma;" "uniform half4 epsilonBase;" "uniform half4 epsilonOther;" "uniform half W;" "uniform int gainmapIsAlpha;" "uniform int gainmapIsRed;" "uniform int singleChannel;" "uniform int noGamma;" "uniform int isApple;" "uniform half appleG;" "uniform half appleH;" "" "half4 main(float2 coord) {" "half4 S = base.eval(coord);" "half4 G = gainmap.eval(coord);" "if (gainmapIsAlpha == 1) {" "G = half4(G.a, G.a, G.a, 1.0);" "}" "if (gainmapIsRed == 1) {" "G = half4(G.r, G.r, G.r, 1.0);" "}" "if (singleChannel == 1) {" "half L;" "if (isApple == 1) {" "L = pow(G.r, appleG);" "L = log(1.0 + (appleH - 1.0) * pow(G.r, appleG));" "} else if (noGamma == 1) {" "L = mix(logRatioMin.r, logRatioMax.r, G.r);" "} else {" "L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));" "}" "half3 H = (S.rgb + epsilonBase.rgb) * exp(L * W) - epsilonOther.rgb;" "return half4(H.r, H.g, H.b, S.a);" "} else {" "half3 L;" "if (isApple == 1) {" "L = pow(G.rgb, half3(appleG));" "L = log(half3(1.0) + (appleH - 1.0) * L);" "} else if (noGamma == 1) {" "L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);" "} else {" "L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));" "}" "half3 H = (S.rgb + epsilonBase.rgb) * exp(L * W) - epsilonOther.rgb;" "return half4(H.r, H.g, H.b, S.a);" "}" "}"; static sk_sp gainmap_apply_effect() { static const SkRuntimeEffect* effect = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {}).effect.release(); SkASSERT(effect); return sk_ref_sp(effect); } static bool all_channels_equal(const SkColor4f& c) { return c.fR == c.fG && c.fR == c.fB; } sk_sp SkGainmapShader::Make(const sk_sp& baseImage, const SkRect& baseRect, const SkSamplingOptions& baseSamplingOptions, const sk_sp& gainmapImage, const SkRect& gainmapRect, const SkSamplingOptions& gainmapSamplingOptions, const SkGainmapInfo& gainmapInfo, const SkRect& dstRect, float dstHdrRatio, sk_sp dstColorSpace) { sk_sp baseColorSpace = baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB(); // Determine the color space in which the gainmap math is to be applied. sk_sp gainmapMathColorSpace = gainmapInfo.fGainmapMathColorSpace ? gainmapInfo.fGainmapMathColorSpace->makeLinearGamma() : baseColorSpace->makeLinearGamma(); if (!dstColorSpace) { dstColorSpace = SkColorSpace::MakeSRGB(); } // Compute the sampling transformation matrices. const SkMatrix baseRectToDstRect = SkMatrix::RectToRect(baseRect, dstRect); const SkMatrix gainmapRectToDstRect = SkMatrix::RectToRect(gainmapRect, dstRect); // Compute the weight parameter that will be used to blend between the images. float W = 0.f; if (dstHdrRatio > gainmapInfo.fDisplayRatioSdr) { if (dstHdrRatio < gainmapInfo.fDisplayRatioHdr) { W = (std::log(dstHdrRatio) - std::log(gainmapInfo.fDisplayRatioSdr)) / (std::log(gainmapInfo.fDisplayRatioHdr) - std::log(gainmapInfo.fDisplayRatioSdr)); } else { W = 1.f; } } const bool baseImageIsHdr = (gainmapInfo.fBaseImageType == SkGainmapInfo::BaseImageType::kHDR); if (baseImageIsHdr) { W -= 1.f; } // Return the base image directly if the gainmap will not be applied at all. if (W == 0.f) { return baseImage->makeShader(baseSamplingOptions, &baseRectToDstRect); } // Create a color filter to transform from the base image's color space to the color space in // which the gainmap is to be applied. auto colorXformSdrToGainmap = SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace); // Create a color filter to transform from the color space in which the gainmap is applied to // the destination color space. auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(gainmapMathColorSpace, dstColorSpace); // The base image shader will convert into the color space in which the gainmap is applied. auto baseImageShader = baseImage->makeRawShader(baseSamplingOptions, &baseRectToDstRect) ->makeWithColorFilter(colorXformSdrToGainmap); // The gainmap image shader will ignore any color space that the gainmap has. auto gainmapImageShader = gainmapImage->makeRawShader(gainmapSamplingOptions, &gainmapRectToDstRect); // Create the shader to apply the gainmap. sk_sp gainmapMathShader; { SkRuntimeShaderBuilder builder(gainmap_apply_effect()); const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR), std::log(gainmapInfo.fGainmapRatioMin.fG), std::log(gainmapInfo.fGainmapRatioMin.fB), 1.f}); const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR), std::log(gainmapInfo.fGainmapRatioMax.fG), std::log(gainmapInfo.fGainmapRatioMax.fB), 1.f}); const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f && gainmapInfo.fGainmapGamma.fG == 1.f && gainmapInfo.fGainmapGamma.fB == 1.f; const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType()); const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag; const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag; const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) && all_channels_equal(gainmapInfo.fGainmapRatioMin) && all_channels_equal(gainmapInfo.fGainmapRatioMax) && (colorTypeFlags == kGray_SkColorChannelFlag || colorTypeFlags == kAlpha_SkColorChannelFlag || colorTypeFlags == kRed_SkColorChannelFlag); const SkColor4f& epsilonBase = baseImageIsHdr ? gainmapInfo.fEpsilonHdr : gainmapInfo.fEpsilonSdr; const SkColor4f& epsilonOther = baseImageIsHdr ? gainmapInfo.fEpsilonSdr : gainmapInfo.fEpsilonHdr; const int isApple = gainmapInfo.fType == SkGainmapInfo::Type::kApple; const float appleG = 1.961f; const float appleH = gainmapInfo.fDisplayRatioHdr; builder.child("base") = baseImageShader; builder.child("gainmap") = gainmapImageShader; builder.uniform("logRatioMin") = logRatioMin; builder.uniform("logRatioMax") = logRatioMax; builder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma; builder.uniform("epsilonBase") = epsilonBase; builder.uniform("epsilonOther") = epsilonOther; builder.uniform("noGamma") = noGamma; builder.uniform("singleChannel") = singleChannel; builder.uniform("gainmapIsAlpha") = gainmapIsAlpha; builder.uniform("gainmapIsRed") = gainmapIsRed; builder.uniform("W") = W; builder.uniform("isApple") = isApple; builder.uniform("appleG") = appleG; builder.uniform("appleH") = appleH; gainmapMathShader = builder.makeShader(); SkASSERT(gainmapMathShader); } // Return a shader that will apply the gainmap and then convert to the destination color space. return gainmapMathShader->makeWithColorFilter(colorXformGainmapToDst); }