xref: /aosp_15_r20/external/skia/src/shaders/SkPictureShader.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2014 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/shaders/SkPictureShader.h"
9 
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkColorType.h"
14 #include "include/core/SkImage.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkSamplingOptions.h"
17 #include "include/core/SkScalar.h"
18 #include "include/core/SkShader.h"
19 #include "include/core/SkSurface.h"
20 #include "include/core/SkTileMode.h"
21 #include "include/private/base/SkDebug.h"
22 #include "include/private/base/SkFloatingPoint.h"
23 #include "src/base/SkArenaAlloc.h"
24 #include "src/core/SkEffectPriv.h"
25 #include "src/core/SkImageInfoPriv.h"
26 #include "src/core/SkMatrixPriv.h"
27 #include "src/core/SkPicturePriv.h"
28 #include "src/core/SkReadBuffer.h"
29 #include "src/core/SkResourceCache.h"
30 #include "src/core/SkWriteBuffer.h"
31 #include "src/shaders/SkLocalMatrixShader.h"
32 
33 #include <algorithm>
34 #include <cstddef>
35 #include <cstdint>
36 #include <utility>
37 class SkDiscardableMemory;
38 
makeShader(SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkMatrix * localMatrix,const SkRect * tile) const39 sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter,
40                                       const SkMatrix* localMatrix, const SkRect* tile) const {
41     if (localMatrix && !localMatrix->invert(nullptr)) {
42         return nullptr;
43     }
44     return SkPictureShader::Make(sk_ref_sp(this), tmx, tmy, filter, localMatrix, tile);
45 }
46 
47 namespace {
48 static unsigned gImageFromPictureKeyNamespaceLabel;
49 
50 struct ImageFromPictureKey : public SkResourceCache::Key {
51 public:
ImageFromPictureKey__anon12cf26d00111::ImageFromPictureKey52     ImageFromPictureKey(SkColorSpace* colorSpace, SkColorType colorType,
53                         uint32_t pictureID, const SkRect& subset,
54                         SkSize scale, const SkSurfaceProps& surfaceProps)
55         : fColorSpaceXYZHash(colorSpace->toXYZD50Hash())
56         , fColorSpaceTransferFnHash(colorSpace->transferFnHash())
57         , fColorType(static_cast<uint32_t>(colorType))
58         , fSubset(subset)
59         , fScale(scale)
60         , fSurfaceProps(surfaceProps)
61     {
62         static const size_t keySize = sizeof(fColorSpaceXYZHash) +
63                                       sizeof(fColorSpaceTransferFnHash) +
64                                       sizeof(fColorType) +
65                                       sizeof(fSubset) +
66                                       sizeof(fScale) +
67                                       sizeof(fSurfaceProps);
68         // This better be packed.
69         SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - &fColorSpaceXYZHash) == keySize);
70         this->init(&gImageFromPictureKeyNamespaceLabel,
71                    SkPicturePriv::MakeSharedID(pictureID),
72                    keySize);
73     }
74 
75 private:
76     uint32_t       fColorSpaceXYZHash;
77     uint32_t       fColorSpaceTransferFnHash;
78     uint32_t       fColorType;
79     SkRect         fSubset;
80     SkSize         fScale;
81     SkSurfaceProps fSurfaceProps;
82 
83     SkDEBUGCODE(uint32_t fEndOfStruct;)
84 };
85 
86 struct ImageFromPictureRec : public SkResourceCache::Rec {
ImageFromPictureRec__anon12cf26d00111::ImageFromPictureRec87     ImageFromPictureRec(const ImageFromPictureKey& key, sk_sp<SkImage> image)
88         : fKey(key)
89         , fImage(std::move(image)) {}
90 
91     ImageFromPictureKey fKey;
92     sk_sp<SkImage>  fImage;
93 
getKey__anon12cf26d00111::ImageFromPictureRec94     const Key& getKey() const override { return fKey; }
bytesUsed__anon12cf26d00111::ImageFromPictureRec95     size_t bytesUsed() const override {
96         // Just the record overhead -- the actual pixels are accounted by SkImage_Lazy.
97         return sizeof(fKey) + (size_t)fImage->width() * fImage->height() * 4;
98     }
getCategory__anon12cf26d00111::ImageFromPictureRec99     const char* getCategory() const override { return "bitmap-shader"; }
diagnostic_only_getDiscardable__anon12cf26d00111::ImageFromPictureRec100     SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; }
101 
Visitor__anon12cf26d00111::ImageFromPictureRec102     static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) {
103         const ImageFromPictureRec& rec = static_cast<const ImageFromPictureRec&>(baseRec);
104         sk_sp<SkImage>* result = reinterpret_cast<sk_sp<SkImage>*>(contextShader);
105 
106         *result = rec.fImage;
107         return true;
108     }
109 };
110 
111 } // namespace
112 
SkPictureShader(sk_sp<SkPicture> picture,SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkRect * tile)113 SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture,
114                                  SkTileMode tmx,
115                                  SkTileMode tmy,
116                                  SkFilterMode filter,
117                                  const SkRect* tile)
118         : fPicture(std::move(picture))
119         , fTile(tile ? *tile : fPicture->cullRect())
120         , fTmx(tmx)
121         , fTmy(tmy)
122         , fFilter(filter) {}
123 
Make(sk_sp<SkPicture> picture,SkTileMode tmx,SkTileMode tmy,SkFilterMode filter,const SkMatrix * lm,const SkRect * tile)124 sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, SkTileMode tmx, SkTileMode tmy,
125                                       SkFilterMode filter, const SkMatrix* lm, const SkRect* tile) {
126     if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) {
127         return SkShaders::Empty();
128     }
129     return SkLocalMatrixShader::MakeWrapped<SkPictureShader>(lm,
130                                                              std::move(picture),
131                                                              tmx, tmy,
132                                                              filter,
133                                                              tile);
134 }
135 
CreateProc(SkReadBuffer & buffer)136 sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) {
137     SkMatrix lm;
138     if (buffer.isVersionLT(SkPicturePriv::Version::kNoShaderLocalMatrix)) {
139         buffer.readMatrix(&lm);
140     }
141     auto tmx = buffer.read32LE(SkTileMode::kLastTileMode);
142     auto tmy = buffer.read32LE(SkTileMode::kLastTileMode);
143     SkRect tile = buffer.readRect();
144 
145     sk_sp<SkPicture> picture;
146 
147     SkFilterMode filter = SkFilterMode::kNearest;
148     if (buffer.isVersionLT(SkPicturePriv::kNoFilterQualityShaders_Version)) {
149         if (buffer.isVersionLT(SkPicturePriv::kPictureShaderFilterParam_Version)) {
150             bool didSerialize = buffer.readBool();
151             if (didSerialize) {
152                 picture = SkPicturePriv::MakeFromBuffer(buffer);
153             }
154         } else {
155             unsigned legacyFilter = buffer.read32();
156             if (legacyFilter <= (unsigned)SkFilterMode::kLast) {
157                 filter = (SkFilterMode)legacyFilter;
158             }
159             picture = SkPicturePriv::MakeFromBuffer(buffer);
160         }
161     } else {
162         filter = buffer.read32LE(SkFilterMode::kLast);
163         picture = SkPicturePriv::MakeFromBuffer(buffer);
164     }
165     return SkPictureShader::Make(picture, tmx, tmy, filter, &lm, &tile);
166 }
167 
flatten(SkWriteBuffer & buffer) const168 void SkPictureShader::flatten(SkWriteBuffer& buffer) const {
169     buffer.write32((unsigned)fTmx);
170     buffer.write32((unsigned)fTmy);
171     buffer.writeRect(fTile);
172     buffer.write32((unsigned)fFilter);
173     SkPicturePriv::Flatten(fPicture, buffer);
174 }
175 
ref_or_srgb(SkColorSpace * cs)176 static sk_sp<SkColorSpace> ref_or_srgb(SkColorSpace* cs) {
177     return cs ? sk_ref_sp(cs) : SkColorSpace::MakeSRGB();
178 }
179 
Make(const SkRect & bounds,const SkMatrix & totalM,SkColorType dstColorType,SkColorSpace * dstColorSpace,const int maxTextureSize,const SkSurfaceProps & propsIn)180 SkPictureShader::CachedImageInfo SkPictureShader::CachedImageInfo::Make(
181         const SkRect& bounds,
182         const SkMatrix& totalM,
183         SkColorType dstColorType,
184         SkColorSpace* dstColorSpace,
185         const int maxTextureSize,
186         const SkSurfaceProps& propsIn) {
187     SkSurfaceProps props = propsIn.cloneWithPixelGeometry(kUnknown_SkPixelGeometry);
188 
189     const SkSize scaledSize = [&]() {
190         SkSize size;
191         // Use a rotation-invariant scale
192         if (!totalM.decomposeScale(&size, nullptr)) {
193             SkPoint center = {bounds.centerX(), bounds.centerY()};
194             SkScalar area = SkMatrixPriv::DifferentialAreaScale(totalM, center);
195             if (!SkIsFinite(area) || SkScalarNearlyZero(area)) {
196                 size = {1, 1};  // ill-conditioned matrix
197             } else {
198                 size.fWidth = size.fHeight = SkScalarSqrt(area);
199             }
200         }
201         size.fWidth *= bounds.width();
202         size.fHeight *= bounds.height();
203 
204         // Clamp the tile size to about 4M pixels
205         static const SkScalar kMaxTileArea = 2048 * 2048;
206         SkScalar tileArea = size.width() * size.height();
207         if (tileArea > kMaxTileArea) {
208             SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
209             size.set(size.width() * clampScale, size.height() * clampScale);
210         }
211 
212         // Scale down the tile size if larger than maxTextureSize for GPU path
213         // or it should fail on create texture
214         if (maxTextureSize) {
215             if (size.width() > maxTextureSize || size.height() > maxTextureSize) {
216                 SkScalar downScale = maxTextureSize / std::max(size.width(), size.height());
217                 size.set(SkScalarFloorToScalar(size.width() * downScale),
218                          SkScalarFloorToScalar(size.height() * downScale));
219             }
220         }
221         return size;
222     }();
223 
224     const SkISize tileSize = scaledSize.toCeil();
225     if (tileSize.isEmpty()) {
226         return {false, {}, {}, {}, {}};
227     }
228 
229     const SkSize tileScale = {tileSize.width() / bounds.width(),
230                               tileSize.height() / bounds.height()};
231     auto imgCS = ref_or_srgb(dstColorSpace);
232     const SkColorType imgCT = SkColorTypeMaxBitsPerChannel(dstColorType) <= 8
233                                       ? kRGBA_8888_SkColorType
234                                       : kRGBA_F16Norm_SkColorType;
235 
236     return {true,
237             tileScale,
238             SkMatrix::RectToRect(bounds, SkRect::MakeIWH(tileSize.width(), tileSize.height())),
239             SkImageInfo::Make(tileSize, imgCT, kPremul_SkAlphaType, imgCS),
240             props};
241 }
242 
makeImage(sk_sp<SkSurface> surf,const SkPicture * pict) const243 sk_sp<SkImage> SkPictureShader::CachedImageInfo::makeImage(sk_sp<SkSurface> surf,
244                                                            const SkPicture* pict) const {
245     if (!surf) {
246         return nullptr;
247     }
248     auto canvas = surf->getCanvas();
249     canvas->concat(matrixForDraw);
250     canvas->drawPicture(pict);
251     return surf->makeImageSnapshot();
252 }
253 
254 // Returns a cached image shader, which wraps a single picture tile at the given
255 // CTM/local matrix.  Also adjusts the local matrix for tile scaling.
rasterShader(const SkMatrix & totalM,SkColorType dstColorType,SkColorSpace * dstColorSpace,const SkSurfaceProps & propsIn) const256 sk_sp<SkShader> SkPictureShader::rasterShader(const SkMatrix& totalM,
257                                               SkColorType dstColorType,
258                                               SkColorSpace* dstColorSpace,
259                                               const SkSurfaceProps& propsIn) const {
260     const int maxTextureSize_NotUsedForCPU = 0;
261     CachedImageInfo info = CachedImageInfo::Make(fTile,
262                                                  totalM,
263                                                  dstColorType, dstColorSpace,
264                                                  maxTextureSize_NotUsedForCPU,
265                                                  propsIn);
266     if (!info.success) {
267         return nullptr;
268     }
269 
270     ImageFromPictureKey key(info.imageInfo.colorSpace(), info.imageInfo.colorType(),
271                             fPicture->uniqueID(), fTile, info.tileScale, info.props);
272 
273     sk_sp<SkImage> image;
274     if (!SkResourceCache::Find(key, ImageFromPictureRec::Visitor, &image)) {
275         image = info.makeImage(SkSurfaces::Raster(info.imageInfo, &info.props), fPicture.get());
276         if (!image) {
277             return nullptr;
278         }
279 
280         SkResourceCache::Add(new ImageFromPictureRec(key, image));
281         SkPicturePriv::AddedToCache(fPicture.get());
282     }
283     // Scale the image to the original picture size.
284     auto lm = SkMatrix::Scale(1.f/info.tileScale.width(), 1.f/info.tileScale.height());
285     return image->makeShader(fTmx, fTmy, SkSamplingOptions(fFilter), &lm);
286 }
287 
appendStages(const SkStageRec & rec,const SkShaders::MatrixRec & mRec) const288 bool SkPictureShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
289     // Keep bitmapShader alive by using alloc instead of stack memory
290     auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>();
291     // We don't check whether the total local matrix is valid here because we have to assume *some*
292     // mapping to make an image. It could be wildly wrong if there is a runtime shader transforming
293     // the coordinates in a manner we don't know about here. However, that is a fundamental problem
294     // with the technique of converting a picture to an image to implement this shader.
295     bitmapShader = this->rasterShader(mRec.totalMatrix(),
296                                       rec.fDstColorType,
297                                       rec.fDstCS,
298                                       rec.fSurfaceProps);
299     if (!bitmapShader) {
300         return false;
301     }
302     return as_SB(bitmapShader)->appendStages(rec, mRec);
303 }
304 
305 /////////////////////////////////////////////////////////////////////////////////////////
306 
307 #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
onMakeContext(const ContextRec & rec,SkArenaAlloc * alloc) const308 SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec,
309                                                       SkArenaAlloc* alloc) const {
310     sk_sp<SkShader> bitmapShader = this->rasterShader(
311             rec.fMatrixRec.totalMatrix(), rec.fDstColorType, rec.fDstColorSpace, rec.fProps);
312     if (!bitmapShader) {
313         return nullptr;
314     }
315 
316     return as_SB(bitmapShader)->makeContext(rec, alloc);
317 }
318 #endif
319