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