/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/third_party/skcms/skcms.h" #include "src/core/SkColorSpacePriv.h" #include "src/core/SkColorSpaceXformSteps.h" #include "src/core/SkRasterPipeline.h" // TODO(mtklein): explain the logic of this file bool SkColorSpaceXformSteps::Required(SkColorSpace* src, SkColorSpace* dst) { // Any SkAlphaType will work fine here as long as we use the same one. SkAlphaType at = kPremul_SkAlphaType; return 0 != SkColorSpaceXformSteps(src, at, dst, at).flags.mask(); // TODO(mtklein): quicker impl. that doesn't construct an SkColorSpaceXformSteps? } SkColorSpaceXformSteps::SkColorSpaceXformSteps(SkColorSpace* src, SkAlphaType srcAT, SkColorSpace* dst, SkAlphaType dstAT) { // Opaque outputs are treated as the same alpha type as the source input. // TODO: we'd really like to have a good way of explaining why we think this is useful. if (dstAT == kOpaque_SkAlphaType) { dstAT = srcAT; } // We have some options about what to do with null src or dst here. // This pair seems to be the most consistent with legacy expectations. if (!src) { src = sk_srgb_singleton(); } if (!dst) { dst = src; } if (src->hash() == dst->hash() && srcAT == dstAT) { SkASSERT(SkColorSpace::Equals(src,dst)); return; } this->flags.unpremul = srcAT == kPremul_SkAlphaType; this->flags.linearize = !src->gammaIsLinear(); this->flags.gamut_transform = src->toXYZD50Hash() != dst->toXYZD50Hash(); this->flags.encode = !dst->gammaIsLinear(); this->flags.premul = srcAT != kOpaque_SkAlphaType && dstAT == kPremul_SkAlphaType; if (this->flags.gamut_transform) { float row_major[9]; // TODO: switch src_to_dst_matrix to row-major src->gamutTransformTo(dst, row_major); this->src_to_dst_matrix[0] = row_major[0]; this->src_to_dst_matrix[1] = row_major[3]; this->src_to_dst_matrix[2] = row_major[6]; this->src_to_dst_matrix[3] = row_major[1]; this->src_to_dst_matrix[4] = row_major[4]; this->src_to_dst_matrix[5] = row_major[7]; this->src_to_dst_matrix[6] = row_major[2]; this->src_to_dst_matrix[7] = row_major[5]; this->src_to_dst_matrix[8] = row_major[8]; } else { #ifdef SK_DEBUG skcms_Matrix3x3 srcM, dstM; src->toXYZD50(&srcM); dst->toXYZD50(&dstM); SkASSERT(0 == memcmp(&srcM, &dstM, 9*sizeof(float)) && "Hash collision"); #endif } // Fill out all the transfer functions we'll use. src-> transferFn(&this->srcTF .g); dst->invTransferFn(&this->dstTFInv.g); this->srcTF_is_sRGB = src->gammaCloseToSRGB(); this->dstTF_is_sRGB = dst->gammaCloseToSRGB(); // If we linearize then immediately reencode with the same transfer function, skip both. if ( this->flags.linearize && !this->flags.gamut_transform && this->flags.encode && src->transferFnHash() == dst->transferFnHash()) { #ifdef SK_DEBUG float dstTF[7]; dst->transferFn(dstTF); for (int i = 0; i < 7; i++) { SkASSERT( (&srcTF.g)[i] == dstTF[i] && "Hash collision" ); } #endif this->flags.linearize = false; this->flags.encode = false; } // Skip unpremul...premul if there are no non-linear operations between. if ( this->flags.unpremul && !this->flags.linearize && !this->flags.encode && this->flags.premul) { this->flags.unpremul = false; this->flags.premul = false; } } void SkColorSpaceXformSteps::apply(float* rgba) const { if (flags.unpremul) { // I don't know why isfinite(x) stopped working on the Chromecast bots... auto is_finite = [](float x) { return x*0 == 0; }; float invA = is_finite(1.0f / rgba[3]) ? 1.0f / rgba[3] : 0; rgba[0] *= invA; rgba[1] *= invA; rgba[2] *= invA; } if (flags.linearize) { skcms_TransferFunction tf; memcpy(&tf, &srcTF, 7*sizeof(float)); rgba[0] = skcms_TransferFunction_eval(&tf, rgba[0]); rgba[1] = skcms_TransferFunction_eval(&tf, rgba[1]); rgba[2] = skcms_TransferFunction_eval(&tf, rgba[2]); } if (flags.gamut_transform) { float temp[3] = { rgba[0], rgba[1], rgba[2] }; for (int i = 0; i < 3; ++i) { rgba[i] = src_to_dst_matrix[ i] * temp[0] + src_to_dst_matrix[3 + i] * temp[1] + src_to_dst_matrix[6 + i] * temp[2]; } } if (flags.encode) { skcms_TransferFunction tf; memcpy(&tf, &dstTFInv, 7*sizeof(float)); rgba[0] = skcms_TransferFunction_eval(&tf, rgba[0]); rgba[1] = skcms_TransferFunction_eval(&tf, rgba[1]); rgba[2] = skcms_TransferFunction_eval(&tf, rgba[2]); } if (flags.premul) { rgba[0] *= rgba[3]; rgba[1] *= rgba[3]; rgba[2] *= rgba[3]; } } void SkColorSpaceXformSteps::apply(SkRasterPipeline* p, bool src_is_normalized) const { #if defined(SK_LEGACY_SRGB_STAGE_CHOICE) src_is_normalized = true; #endif if (flags.unpremul) { p->append(SkRasterPipeline::unpremul); } if (flags.linearize) { if (src_is_normalized && srcTF_is_sRGB) { p->append(SkRasterPipeline::from_srgb); } else if (srcTF.a == 1 && srcTF.b == 0 && srcTF.c == 0 && srcTF.d == 0 && srcTF.e == 0 && srcTF.f == 0) { p->append(SkRasterPipeline::gamma_, &srcTF.g); } else { p->append(SkRasterPipeline::parametric, &srcTF); } } if (flags.gamut_transform) { p->append(SkRasterPipeline::matrix_3x3, &src_to_dst_matrix); } if (flags.encode) { if (src_is_normalized && dstTF_is_sRGB) { p->append(SkRasterPipeline::to_srgb); } else if (dstTFInv.a == 1 && dstTFInv.b == 0 && dstTFInv.c == 0 && dstTFInv.d == 0 && dstTFInv.e == 0 && dstTFInv.f == 0) { p->append(SkRasterPipeline::gamma_, &dstTFInv.g); } else { p->append(SkRasterPipeline::parametric, &dstTFInv); } } if (flags.premul) { p->append(SkRasterPipeline::premul); } }