/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkColorFilter.h" #include "SkColorSpacePriv.h" #include "SkColorSpaceXformer.h" #include "SkColorSpaceXformPriv.h" #include "SkDrawLooper.h" #include "SkGradientShader.h" #include "SkImage.h" #include "SkImage_Base.h" #include "SkImageFilter.h" #include "SkImagePriv.h" #include "SkShaderBase.h" SkColorSpaceXformer::SkColorSpaceXformer(sk_sp dst, std::unique_ptr fromSRGB) : fDst(std::move(dst)) , fFromSRGB(std::move(fromSRGB)) , fReentryCount(0) {} SkColorSpaceXformer::~SkColorSpaceXformer() {} std::unique_ptr SkColorSpaceXformer::Make(sk_sp dst) { std::unique_ptr fromSRGB = SkMakeColorSpaceXform( sk_srgb_singleton(), dst.get()); return fromSRGB ? std::unique_ptr(new SkColorSpaceXformer(std::move(dst), std::move(fromSRGB))) : nullptr; } // So what's up with these caches? // // We want to cache transformed objects for a couple of reasons: // // 1) to avoid redundant work - the inputs are a DAG, not a tree (e.g. same SkImage drawn multiple // times in a SkPicture), so if we blindly recurse we could end up transforming the same objects // repeatedly. // // 2) to avoid topology changes - we want the output to remain isomorphic with the input -- this is // particularly important for image filters (to maintain their original DAG structure in order // to not defeat their own/internal caching), but also for avoiding unnecessary cloning // (e.g. duplicated SkImages allocated for the example in #1 above). // // The caching scope is naturaly bound by the lifetime of the SkColorSpaceXformer object, but // clients may choose to not discard xformers immediately - in which case, caching indefinitely // is problematic. The solution is to limit the cache scope to the top level apply() call // (i.e. we only keep cached objects alive while transforming). class SkColorSpaceXformer::AutoCachePurge { public: AutoCachePurge(SkColorSpaceXformer* xformer) : fXformer(xformer) { fXformer->fReentryCount++; } ~AutoCachePurge() { SkASSERT(fXformer->fReentryCount > 0); if (--fXformer->fReentryCount == 0) { fXformer->purgeCaches(); } } private: SkColorSpaceXformer* fXformer; }; template sk_sp SkColorSpaceXformer::cachedApply(const T* src, Cache* cache, sk_sp (*applyFunc)(const T*, SkColorSpaceXformer*)) { if (!src) { return nullptr; } auto key = sk_ref_sp(const_cast(src)); if (auto* xformed = cache->find(key)) { return sk_ref_sp(xformed->get()); } auto xformed = applyFunc(src, this); cache->set(std::move(key), xformed); return xformed; } void SkColorSpaceXformer::purgeCaches() { fImageCache.reset(); fColorFilterCache.reset(); fImageFilterCache.reset(); } sk_sp SkColorSpaceXformer::apply(const SkImage* src) { const AutoCachePurge autoPurge(this); return this->cachedApply(src, &fImageCache, [](const SkImage* img, SkColorSpaceXformer* xformer) { return img->makeColorSpace(xformer->fDst); }); } sk_sp SkColorSpaceXformer::apply(const SkBitmap& src) { const AutoCachePurge autoPurge(this); sk_sp image = SkMakeImageFromRasterBitmap(src, kNever_SkCopyPixelsMode); if (!image) { return nullptr; } sk_sp xformed = image->makeColorSpace(fDst); // We want to be sure we don't let the kNever_SkCopyPixelsMode image escape this stack frame. SkASSERT(xformed != image); return xformed; } sk_sp SkColorSpaceXformer::apply(const SkColorFilter* colorFilter) { const AutoCachePurge autoPurge(this); return this->cachedApply(colorFilter, &fColorFilterCache, [](const SkColorFilter* f, SkColorSpaceXformer* xformer) { return f->makeColorSpace(xformer); }); } sk_sp SkColorSpaceXformer::apply(const SkImageFilter* imageFilter) { const AutoCachePurge autoPurge(this); return this->cachedApply(imageFilter, &fImageFilterCache, [](const SkImageFilter* f, SkColorSpaceXformer* xformer) { return f->makeColorSpace(xformer); }); } sk_sp SkColorSpaceXformer::apply(const SkShader* shader) { const AutoCachePurge autoPurge(this); return as_SB(shader)->makeColorSpace(this); } void SkColorSpaceXformer::apply(SkColor* xformed, const SkColor* srgb, int n) { SkAssertResult(fFromSRGB->apply(SkColorSpaceXform::kBGRA_8888_ColorFormat, xformed, SkColorSpaceXform::kBGRA_8888_ColorFormat, srgb, n, kUnpremul_SkAlphaType)); } SkColor SkColorSpaceXformer::apply(SkColor srgb) { SkColor xformed; this->apply(&xformed, &srgb, 1); return xformed; } SkPaint SkColorSpaceXformer::apply(const SkPaint& src) { const AutoCachePurge autoPurge(this); SkPaint dst = src; // All SkColorSpaces have the same black point. if (src.getColor() & 0xffffff) { dst.setColor(this->apply(src.getColor())); } if (auto shader = src.getShader()) { dst.setShader(this->apply(shader)); } if (auto cf = src.getColorFilter()) { dst.setColorFilter(this->apply(cf)); } if (auto looper = src.getDrawLooper()) { dst.setDrawLooper(looper->makeColorSpace(this)); } if (auto imageFilter = src.getImageFilter()) { dst.setImageFilter(this->apply(imageFilter)); } return dst; } SkCanvas::Lattice SkColorSpaceXformer::apply(const SkCanvas::Lattice& lattice, SkColor* colorBuffer, int count) { if (count) { this->apply(colorBuffer, lattice.fColors, count); return {lattice.fXDivs, lattice.fYDivs, lattice.fRectTypes, lattice.fXCount, lattice.fYCount, lattice.fBounds, colorBuffer}; } return lattice; }