1 | /* |
2 | * Copyright 2015 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/SkImageShader.h" |
9 | |
10 | #include "src/core/SkArenaAlloc.h" |
11 | #include "src/core/SkBitmapController.h" |
12 | #include "src/core/SkColorSpacePriv.h" |
13 | #include "src/core/SkColorSpaceXformSteps.h" |
14 | #include "src/core/SkMatrixProvider.h" |
15 | #include "src/core/SkOpts.h" |
16 | #include "src/core/SkRasterPipeline.h" |
17 | #include "src/core/SkReadBuffer.h" |
18 | #include "src/core/SkScopeExit.h" |
19 | #include "src/core/SkVM.h" |
20 | #include "src/core/SkWriteBuffer.h" |
21 | #include "src/image/SkImage_Base.h" |
22 | #include "src/shaders/SkBitmapProcShader.h" |
23 | #include "src/shaders/SkEmptyShader.h" |
24 | |
25 | SkM44 SkImageShader::CubicResamplerMatrix(float B, float C) { |
26 | const float scale = 1.0f/18; |
27 | B *= scale; |
28 | C *= scale; |
29 | return SkM44( 3*B, -9*B - 18*C, 9*B + 36*C, -3*B - 18*C, |
30 | 1 - 6*B, 0, -3 + 36*B + 18*C, 2 - 27*B - 18*C, |
31 | 3*B, 9*B + 18*C, 3 - 45*B - 36*C, -2 + 27*B + 18*C, |
32 | 0, 0, -18*C, 3*B + 18*C); |
33 | } |
34 | |
35 | /** |
36 | * We are faster in clamp, so always use that tiling when we can. |
37 | */ |
38 | static SkTileMode optimize(SkTileMode tm, int dimension) { |
39 | SkASSERT(dimension > 0); |
40 | #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK |
41 | // need to update frameworks/base/libs/hwui/tests/unit/SkiaBehaviorTests.cpp:55 to allow |
42 | // for transforming to clamp. |
43 | return tm; |
44 | #else |
45 | return dimension == 1 ? SkTileMode::kClamp : tm; |
46 | #endif |
47 | } |
48 | |
49 | SkImageShader::SkImageShader(sk_sp<SkImage> img, |
50 | SkTileMode tmx, SkTileMode tmy, |
51 | const SkMatrix* localMatrix, |
52 | FilterEnum filtering, |
53 | bool clampAsIfUnpremul) |
54 | : INHERITED(localMatrix) |
55 | , fImage(std::move(img)) |
56 | , fTileModeX(optimize(tmx, fImage->width())) |
57 | , fTileModeY(optimize(tmy, fImage->height())) |
58 | , fFilterEnum(filtering) |
59 | , fClampAsIfUnpremul(clampAsIfUnpremul) |
60 | , fFilterOptions({}) // ignored |
61 | { |
62 | SkASSERT(filtering != kUseFilterOptions); |
63 | } |
64 | |
65 | SkImageShader::SkImageShader(sk_sp<SkImage> img, |
66 | SkTileMode tmx, SkTileMode tmy, |
67 | const SkFilterOptions& options, |
68 | const SkMatrix* localMatrix) |
69 | : INHERITED(localMatrix) |
70 | , fImage(std::move(img)) |
71 | , fTileModeX(optimize(tmx, fImage->width())) |
72 | , fTileModeY(optimize(tmy, fImage->height())) |
73 | , fFilterEnum(FilterEnum::kUseFilterOptions) |
74 | , fClampAsIfUnpremul(false) |
75 | , fFilterOptions(options) |
76 | {} |
77 | |
78 | SkImageShader::SkImageShader(sk_sp<SkImage> img, |
79 | SkTileMode tmx, SkTileMode tmy, |
80 | SkImage::CubicResampler cubic, |
81 | const SkMatrix* localMatrix) |
82 | : INHERITED(localMatrix) |
83 | , fImage(std::move(img)) |
84 | , fTileModeX(optimize(tmx, fImage->width())) |
85 | , fTileModeY(optimize(tmy, fImage->height())) |
86 | , fFilterEnum(FilterEnum::kUseCubicResampler) |
87 | , fClampAsIfUnpremul(false) |
88 | , fFilterOptions({}) // ignored |
89 | , fCubic(cubic) |
90 | {} |
91 | |
92 | // fClampAsIfUnpremul is always false when constructed through public APIs, |
93 | // so there's no need to read or write it here. |
94 | |
95 | sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) { |
96 | auto tmx = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode); |
97 | auto tmy = buffer.read32LE<SkTileMode>(SkTileMode::kLastTileMode); |
98 | |
99 | FilterEnum fe = kInheritFromPaint; |
100 | if (!buffer.isVersionLT(SkPicturePriv::kFilterEnumInImageShader_Version)) { |
101 | fe = buffer.read32LE<FilterEnum>(kLast); |
102 | } |
103 | |
104 | SkFilterOptions fo{ SkSamplingMode::kNearest, SkMipmapMode::kNone }; |
105 | SkImage::CubicResampler cubic{}; |
106 | |
107 | if (buffer.isVersionLT(SkPicturePriv::kCubicResamplerImageShader_Version)) { |
108 | if (!buffer.isVersionLT(SkPicturePriv::kFilterOptionsInImageShader_Version)) { |
109 | fo.fSampling = buffer.read32LE<SkSamplingMode>(SkSamplingMode::kLinear); |
110 | fo.fMipmap = buffer.read32LE<SkMipmapMode>(SkMipmapMode::kLinear); |
111 | } |
112 | } else { |
113 | switch (fe) { |
114 | case kUseFilterOptions: |
115 | fo.fSampling = buffer.read32LE<SkSamplingMode>(SkSamplingMode::kLinear); |
116 | fo.fMipmap = buffer.read32LE<SkMipmapMode>(SkMipmapMode::kLinear); |
117 | break; |
118 | case kUseCubicResampler: |
119 | cubic.B = buffer.readScalar(); |
120 | cubic.C = buffer.readScalar(); |
121 | break; |
122 | default: |
123 | break; |
124 | } |
125 | } |
126 | |
127 | SkMatrix localMatrix; |
128 | buffer.readMatrix(&localMatrix); |
129 | sk_sp<SkImage> img = buffer.readImage(); |
130 | if (!img) { |
131 | return nullptr; |
132 | } |
133 | |
134 | switch (fe) { |
135 | case kUseFilterOptions: |
136 | return SkImageShader::Make(std::move(img), tmx, tmy, fo, &localMatrix); |
137 | case kUseCubicResampler: |
138 | return SkImageShader::Make(std::move(img), tmx, tmy, cubic, &localMatrix); |
139 | default: |
140 | break; |
141 | } |
142 | return SkImageShader::Make(std::move(img), tmx, tmy, &localMatrix, fe); |
143 | } |
144 | |
145 | void SkImageShader::flatten(SkWriteBuffer& buffer) const { |
146 | buffer.writeUInt((unsigned)fTileModeX); |
147 | buffer.writeUInt((unsigned)fTileModeY); |
148 | buffer.writeUInt((unsigned)fFilterEnum); |
149 | switch (fFilterEnum) { |
150 | case kUseCubicResampler: |
151 | buffer.writeScalar(fCubic.B); |
152 | buffer.writeScalar(fCubic.C); |
153 | break; |
154 | case kUseFilterOptions: |
155 | buffer.writeUInt((unsigned)fFilterOptions.fSampling); |
156 | buffer.writeUInt((unsigned)fFilterOptions.fMipmap); |
157 | break; |
158 | default: |
159 | break; |
160 | } |
161 | buffer.writeMatrix(this->getLocalMatrix()); |
162 | buffer.writeImage(fImage.get()); |
163 | SkASSERT(fClampAsIfUnpremul == false); |
164 | } |
165 | |
166 | bool SkImageShader::isOpaque() const { |
167 | return fImage->isOpaque() && |
168 | fTileModeX != SkTileMode::kDecal && fTileModeY != SkTileMode::kDecal; |
169 | } |
170 | |
171 | #ifdef SK_ENABLE_LEGACY_SHADERCONTEXT |
172 | static bool legacy_shader_can_handle(const SkMatrix& inv) { |
173 | SkASSERT(!inv.hasPerspective()); |
174 | |
175 | // Scale+translate methods are always present, but affine might not be. |
176 | if (!SkOpts::S32_alpha_D32_filter_DXDY && !inv.isScaleTranslate()) { |
177 | return false; |
178 | } |
179 | |
180 | // legacy code uses SkFixed 32.32, so ensure the inverse doesn't map device coordinates |
181 | // out of range. |
182 | const SkScalar max_dev_coord = 32767.0f; |
183 | const SkRect src = inv.mapRect(SkRect::MakeWH(max_dev_coord, max_dev_coord)); |
184 | |
185 | // take 1/4 of max signed 32bits so we have room to subtract local values |
186 | const SkScalar max_fixed32dot32 = float(SK_MaxS32) * 0.25f; |
187 | if (!SkRect::MakeLTRB(-max_fixed32dot32, -max_fixed32dot32, |
188 | +max_fixed32dot32, +max_fixed32dot32).contains(src)) { |
189 | return false; |
190 | } |
191 | |
192 | // legacy shader impl should be able to handle these matrices |
193 | return true; |
194 | } |
195 | |
196 | SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, |
197 | SkArenaAlloc* alloc) const { |
198 | // we only support the old SkFilterQuality setting |
199 | if (fFilterEnum > kInheritFromPaint) { |
200 | return nullptr; |
201 | } |
202 | |
203 | auto quality = this->resolveFiltering(rec.fPaint->getFilterQuality()); |
204 | |
205 | if (quality == kHigh_SkFilterQuality) { |
206 | return nullptr; |
207 | } |
208 | if (fImage->alphaType() == kUnpremul_SkAlphaType) { |
209 | return nullptr; |
210 | } |
211 | if (fImage->colorType() != kN32_SkColorType) { |
212 | return nullptr; |
213 | } |
214 | if (fTileModeX != fTileModeY) { |
215 | return nullptr; |
216 | } |
217 | if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) { |
218 | return nullptr; |
219 | } |
220 | |
221 | // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, |
222 | // so it can't handle bitmaps larger than 65535. |
223 | // |
224 | // We back off another bit to 32767 to make small amounts of |
225 | // intermediate math safe, e.g. in |
226 | // |
227 | // SkFixed fx = ...; |
228 | // fx = tile(fx + SK_Fixed1); |
229 | // |
230 | // we want to make sure (fx + SK_Fixed1) never overflows. |
231 | if (fImage-> width() > 32767 || |
232 | fImage->height() > 32767) { |
233 | return nullptr; |
234 | } |
235 | |
236 | SkMatrix inv; |
237 | if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &inv) || |
238 | !legacy_shader_can_handle(inv)) { |
239 | return nullptr; |
240 | } |
241 | |
242 | if (!rec.isLegacyCompatible(fImage->colorSpace())) { |
243 | return nullptr; |
244 | } |
245 | |
246 | // Send in a modified paint with different filter-quality if we don't agree with the paint |
247 | SkPaint modifiedPaint; |
248 | ContextRec modifiedRec = rec; |
249 | if (quality != rec.fPaint->getFilterQuality()) { |
250 | modifiedPaint = *rec.fPaint; |
251 | modifiedPaint.setFilterQuality(quality); |
252 | modifiedRec.fPaint = &modifiedPaint; |
253 | } |
254 | return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, |
255 | as_IB(fImage.get()), modifiedRec, alloc); |
256 | } |
257 | #endif |
258 | |
259 | SkImage* SkImageShader::onIsAImage(SkMatrix* texM, SkTileMode xy[]) const { |
260 | if (texM) { |
261 | *texM = this->getLocalMatrix(); |
262 | } |
263 | if (xy) { |
264 | xy[0] = fTileModeX; |
265 | xy[1] = fTileModeY; |
266 | } |
267 | return const_cast<SkImage*>(fImage.get()); |
268 | } |
269 | |
270 | sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, |
271 | SkTileMode tmx, SkTileMode tmy, |
272 | const SkMatrix* localMatrix, |
273 | FilterEnum filtering, |
274 | bool clampAsIfUnpremul) { |
275 | if (!image) { |
276 | return sk_make_sp<SkEmptyShader>(); |
277 | } |
278 | return sk_sp<SkShader>{ |
279 | new SkImageShader(image, tmx, tmy, localMatrix, filtering, clampAsIfUnpremul) |
280 | }; |
281 | } |
282 | |
283 | sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, |
284 | SkTileMode tmx, SkTileMode tmy, |
285 | const SkFilterOptions& options, |
286 | const SkMatrix* localMatrix) { |
287 | if (!image) { |
288 | return sk_make_sp<SkEmptyShader>(); |
289 | } |
290 | return sk_sp<SkShader>{ |
291 | new SkImageShader(image, tmx, tmy, options, localMatrix) |
292 | }; |
293 | } |
294 | |
295 | sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, SkTileMode tmx, SkTileMode tmy, |
296 | SkImage::CubicResampler cubic, const SkMatrix* localMatrix) { |
297 | if (!(cubic.B >= 0 && cubic.B <= 1 && |
298 | cubic.C >= 0 && cubic.C <= 1)) { |
299 | return nullptr; |
300 | } |
301 | if (!image) { |
302 | return sk_make_sp<SkEmptyShader>(); |
303 | } |
304 | return sk_sp<SkShader>{ |
305 | new SkImageShader(image, tmx, tmy, cubic, localMatrix) |
306 | }; |
307 | } |
308 | |
309 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
310 | |
311 | #if SK_SUPPORT_GPU |
312 | |
313 | #include "include/gpu/GrRecordingContext.h" |
314 | #include "src/gpu/GrBitmapTextureMaker.h" |
315 | #include "src/gpu/GrCaps.h" |
316 | #include "src/gpu/GrColorInfo.h" |
317 | #include "src/gpu/GrImageTextureMaker.h" |
318 | #include "src/gpu/GrRecordingContextPriv.h" |
319 | #include "src/gpu/GrTextureAdjuster.h" |
320 | #include "src/gpu/SkGr.h" |
321 | #include "src/gpu/effects/GrBicubicEffect.h" |
322 | #include "src/gpu/effects/GrBlendFragmentProcessor.h" |
323 | #include "src/gpu/effects/GrTextureEffect.h" |
324 | |
325 | std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor( |
326 | const GrFPArgs& args) const { |
327 | const auto lm = this->totalLocalMatrix(args.fPreLocalMatrix); |
328 | SkMatrix lmInverse; |
329 | if (!lm->invert(&lmInverse)) { |
330 | return nullptr; |
331 | } |
332 | |
333 | // This would all be much nicer with std::variant. |
334 | static constexpr size_t kSize = std::max({sizeof(GrYUVAImageTextureMaker), |
335 | sizeof(GrTextureAdjuster ), |
336 | sizeof(GrImageTextureMaker ), |
337 | sizeof(GrBitmapTextureMaker )}); |
338 | static constexpr size_t kAlign = std::max({alignof(GrYUVAImageTextureMaker), |
339 | alignof(GrTextureAdjuster ), |
340 | alignof(GrImageTextureMaker ), |
341 | alignof(GrBitmapTextureMaker )}); |
342 | std::aligned_storage_t<kSize, kAlign> storage; |
343 | GrTextureProducer* producer = nullptr; |
344 | SkScopeExit destroyProducer([&producer]{ if (producer) { producer->~GrTextureProducer(); } }); |
345 | |
346 | uint32_t pinnedUniqueID; |
347 | SkBitmap bm; |
348 | if (as_IB(fImage)->isYUVA()) { |
349 | producer = new (&storage) GrYUVAImageTextureMaker(args.fContext, fImage.get()); |
350 | } else if (GrSurfaceProxyView view = |
351 | as_IB(fImage)->refPinnedView(args.fContext, &pinnedUniqueID)) { |
352 | GrColorInfo colorInfo; |
353 | if (args.fContext->priv().caps()->isFormatSRGB(view.proxy()->backendFormat())) { |
354 | SkASSERT(fImage->colorType() == kRGBA_8888_SkColorType); |
355 | colorInfo = GrColorInfo(GrColorType::kRGBA_8888_SRGB, fImage->alphaType(), |
356 | fImage->refColorSpace()); |
357 | } else { |
358 | colorInfo = fImage->imageInfo().colorInfo(); |
359 | } |
360 | producer = new (&storage) |
361 | GrTextureAdjuster(args.fContext, std::move(view), colorInfo, pinnedUniqueID); |
362 | } else if (fImage->isLazyGenerated()) { |
363 | producer = new (&storage) |
364 | GrImageTextureMaker(args.fContext, fImage.get(), GrImageTexGenPolicy::kDraw); |
365 | } else if (as_IB(fImage)->getROPixels(&bm)) { |
366 | producer = |
367 | new (&storage) GrBitmapTextureMaker(args.fContext, bm, GrImageTexGenPolicy::kDraw); |
368 | } else { |
369 | return nullptr; |
370 | } |
371 | GrSamplerState::WrapMode wmX = SkTileModeToWrapMode(fTileModeX), |
372 | wmY = SkTileModeToWrapMode(fTileModeY); |
373 | // Must set wrap and filter on the sampler before requesting a texture. In two places |
374 | // below we check the matrix scale factors to determine how to interpret the filter |
375 | // quality setting. This completely ignores the complexity of the drawVertices case |
376 | // where explicit local coords are provided by the caller. |
377 | bool sharpen = args.fContext->priv().options().fSharpenMipmappedTextures; |
378 | GrSamplerState::Filter fm; |
379 | GrSamplerState::MipmapMode mm; |
380 | bool bicubic; |
381 | if (fFilterEnum == kUseFilterOptions) { |
382 | bicubic = false; |
383 | switch (fFilterOptions.fSampling) { |
384 | case SkSamplingMode::kNearest: fm = GrSamplerState::Filter::kNearest; break; |
385 | case SkSamplingMode::kLinear : fm = GrSamplerState::Filter::kLinear ; break; |
386 | } |
387 | switch (fFilterOptions.fMipmap) { |
388 | case SkMipmapMode::kNone : mm = GrSamplerState::MipmapMode::kNone ; break; |
389 | case SkMipmapMode::kNearest: mm = GrSamplerState::MipmapMode::kNearest; break; |
390 | case SkMipmapMode::kLinear : mm = GrSamplerState::MipmapMode::kLinear ; break; |
391 | } |
392 | } else { |
393 | std::tie(fm, mm, bicubic) = |
394 | GrInterpretFilterQuality(fImage->dimensions(), |
395 | this->resolveFiltering(args.fFilterQuality), |
396 | args.fMatrixProvider.localToDevice(), |
397 | *lm, |
398 | sharpen); |
399 | } |
400 | std::unique_ptr<GrFragmentProcessor> fp; |
401 | if (bicubic) { |
402 | fp = producer->createBicubicFragmentProcessor(lmInverse, nullptr, nullptr, wmX, wmY); |
403 | } else { |
404 | fp = producer->createFragmentProcessor(lmInverse, nullptr, nullptr, {wmX, wmY, fm, mm}); |
405 | } |
406 | if (!fp) { |
407 | return nullptr; |
408 | } |
409 | fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->colorSpace(), producer->alphaType(), |
410 | args.fDstColorInfo->colorSpace(), kPremul_SkAlphaType); |
411 | fp = GrBlendFragmentProcessor::Make(std::move(fp), nullptr, SkBlendMode::kModulate); |
412 | bool isAlphaOnly = SkColorTypeIsAlphaOnly(fImage->colorType()); |
413 | if (isAlphaOnly) { |
414 | return fp; |
415 | } else if (args.fInputColorIsOpaque) { |
416 | return GrFragmentProcessor::OverrideInput(std::move(fp), SK_PMColor4fWHITE, false); |
417 | } |
418 | return GrFragmentProcessor::MulChildByInputAlpha(std::move(fp)); |
419 | } |
420 | |
421 | #endif |
422 | |
423 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
424 | #include "src/core/SkImagePriv.h" |
425 | |
426 | sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkTileMode tmx, SkTileMode tmy, |
427 | const SkMatrix* localMatrix, SkCopyPixelsMode cpm) { |
428 | return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm), |
429 | tmx, tmy, localMatrix, SkImageShader::kInheritFromPaint); |
430 | } |
431 | |
432 | sk_sp<SkShader> SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src, |
433 | SkTileMode tmx, SkTileMode tmy, |
434 | const SkMatrix* localMatrix, SkCopyPixelsMode mode) { |
435 | auto s = SkMakeBitmapShader(src, tmx, tmy, localMatrix, mode); |
436 | if (!s) { |
437 | return nullptr; |
438 | } |
439 | if (src.colorType() == kAlpha_8_SkColorType && paint.getShader()) { |
440 | // Compose the image shader with the paint's shader. Alpha images+shaders should output the |
441 | // texture's alpha multiplied by the shader's color. DstIn (d*sa) will achieve this with |
442 | // the source image and dst shader (MakeBlend takes dst first, src second). |
443 | s = SkShaders::Blend(SkBlendMode::kDstIn, paint.refShader(), std::move(s)); |
444 | } |
445 | return s; |
446 | } |
447 | |
448 | void SkShaderBase::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkImageShader); } |
449 | |
450 | class SkImageStageUpdater : public SkStageUpdater { |
451 | public: |
452 | SkImageStageUpdater(const SkImageShader* shader, bool usePersp) |
453 | : fShader(shader) |
454 | , fUsePersp(usePersp || as_SB(shader)->getLocalMatrix().hasPerspective()) |
455 | {} |
456 | |
457 | const SkImageShader* fShader; |
458 | const bool fUsePersp; // else use affine |
459 | |
460 | // large enough for perspective, though often we just use 2x3 |
461 | float fMatrixStorage[9]; |
462 | |
463 | #if 0 // TODO: when we support mipmaps |
464 | SkRasterPipeline_GatherCtx* fGather; |
465 | SkRasterPipeline_TileCtx* fLimitX; |
466 | SkRasterPipeline_TileCtx* fLimitY; |
467 | SkRasterPipeline_DecalTileCtx* fDecal; |
468 | #endif |
469 | |
470 | void append_matrix_stage(SkRasterPipeline* p) { |
471 | if (fUsePersp) { |
472 | p->append(SkRasterPipeline::matrix_perspective, fMatrixStorage); |
473 | } else { |
474 | p->append(SkRasterPipeline::matrix_2x3, fMatrixStorage); |
475 | } |
476 | } |
477 | |
478 | bool update(const SkMatrix& ctm, const SkMatrix* localM) override { |
479 | SkMatrix matrix; |
480 | if (fShader->computeTotalInverse(ctm, localM, &matrix)) { |
481 | if (fUsePersp) { |
482 | matrix.get9(fMatrixStorage); |
483 | } else { |
484 | // if we get here, matrix should be affine. If it isn't, then defensively we |
485 | // won't draw (by returning false), but we should work to never let this |
486 | // happen (i.e. better preflight by the caller to know ahead of time that we |
487 | // may encounter perspective, either in the CTM, or in the localM). |
488 | // |
489 | // See https://bugs.chromium.org/p/skia/issues/detail?id=10004 |
490 | // |
491 | if (!matrix.asAffine(fMatrixStorage)) { |
492 | SkASSERT(false); |
493 | return false; |
494 | } |
495 | } |
496 | return true; |
497 | } |
498 | return false; |
499 | } |
500 | }; |
501 | |
502 | static void tweak_quality_and_inv_matrix(SkFilterQuality* quality, SkMatrix* matrix) { |
503 | // When the matrix is just an integer translate, bilerp == nearest neighbor. |
504 | if (*quality == kLow_SkFilterQuality && |
505 | matrix->getType() <= SkMatrix::kTranslate_Mask && |
506 | matrix->getTranslateX() == (int)matrix->getTranslateX() && |
507 | matrix->getTranslateY() == (int)matrix->getTranslateY()) { |
508 | *quality = kNone_SkFilterQuality; |
509 | } |
510 | |
511 | // See skia:4649 and the GM image_scale_aligned. |
512 | if (*quality == kNone_SkFilterQuality) { |
513 | if (matrix->getScaleX() >= 0) { |
514 | matrix->setTranslateX(nextafterf(matrix->getTranslateX(), |
515 | floorf(matrix->getTranslateX()))); |
516 | } |
517 | if (matrix->getScaleY() >= 0) { |
518 | matrix->setTranslateY(nextafterf(matrix->getTranslateY(), |
519 | floorf(matrix->getTranslateY()))); |
520 | } |
521 | } |
522 | } |
523 | |
524 | bool SkImageShader::doStages(const SkStageRec& rec, SkImageStageUpdater* updater) const { |
525 | SkFilterQuality quality; |
526 | switch (fFilterEnum) { |
527 | case FilterEnum::kUseFilterOptions: |
528 | case FilterEnum::kUseCubicResampler: |
529 | return false; // TODO: support these in stages |
530 | case FilterEnum::kInheritFromPaint: |
531 | quality = rec.fPaint.getFilterQuality(); |
532 | break; |
533 | default: |
534 | quality = (SkFilterQuality)fFilterEnum; |
535 | break; |
536 | } |
537 | |
538 | if (updater && quality == kMedium_SkFilterQuality) { |
539 | // TODO: medium: recall RequestBitmap and update width/height accordingly |
540 | return false; |
541 | } |
542 | |
543 | SkRasterPipeline* p = rec.fPipeline; |
544 | SkArenaAlloc* alloc = rec.fAlloc; |
545 | |
546 | SkMatrix matrix; |
547 | if (!this->computeTotalInverse(rec.fMatrixProvider.localToDevice(), rec.fLocalM, &matrix)) { |
548 | return false; |
549 | } |
550 | |
551 | const auto* state = SkBitmapController::RequestBitmap(as_IB(fImage.get()), |
552 | matrix, quality, alloc); |
553 | if (!state) { |
554 | return false; |
555 | } |
556 | |
557 | const SkPixmap& pm = state->pixmap(); |
558 | matrix = state->invMatrix(); |
559 | quality = state->quality(); |
560 | auto info = pm.info(); |
561 | |
562 | p->append(SkRasterPipeline::seed_shader); |
563 | |
564 | if (updater) { |
565 | updater->append_matrix_stage(p); |
566 | } else { |
567 | tweak_quality_and_inv_matrix(&quality, &matrix); |
568 | p->append_matrix(alloc, matrix); |
569 | } |
570 | |
571 | auto gather = alloc->make<SkRasterPipeline_GatherCtx>(); |
572 | gather->pixels = pm.addr(); |
573 | gather->stride = pm.rowBytesAsPixels(); |
574 | gather->width = pm.width(); |
575 | gather->height = pm.height(); |
576 | |
577 | auto limit_x = alloc->make<SkRasterPipeline_TileCtx>(), |
578 | limit_y = alloc->make<SkRasterPipeline_TileCtx>(); |
579 | limit_x->scale = pm.width(); |
580 | limit_x->invScale = 1.0f / pm.width(); |
581 | limit_y->scale = pm.height(); |
582 | limit_y->invScale = 1.0f / pm.height(); |
583 | |
584 | SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr; |
585 | bool decal_x_and_y = fTileModeX == SkTileMode::kDecal && fTileModeY == SkTileMode::kDecal; |
586 | if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) { |
587 | decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>(); |
588 | decal_ctx->limit_x = limit_x->scale; |
589 | decal_ctx->limit_y = limit_y->scale; |
590 | } |
591 | |
592 | #if 0 // TODO: when we support kMedium |
593 | if (updator && (quality == kMedium_SkFilterQuality)) { |
594 | // if we change levels in mipmap, we need to update the scales (and invScales) |
595 | updator->fGather = gather; |
596 | updator->fLimitX = limit_x; |
597 | updator->fLimitY = limit_y; |
598 | updator->fDecal = decal_ctx; |
599 | } |
600 | #endif |
601 | |
602 | auto append_tiling_and_gather = [&] { |
603 | if (decal_x_and_y) { |
604 | p->append(SkRasterPipeline::decal_x_and_y, decal_ctx); |
605 | } else { |
606 | switch (fTileModeX) { |
607 | case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */ break; |
608 | case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x, limit_x); break; |
609 | case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x, limit_x); break; |
610 | case SkTileMode::kDecal: p->append(SkRasterPipeline::decal_x, decal_ctx); break; |
611 | } |
612 | switch (fTileModeY) { |
613 | case SkTileMode::kClamp: /* The gather_xxx stage will clamp for us. */ break; |
614 | case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_y, limit_y); break; |
615 | case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_y, limit_y); break; |
616 | case SkTileMode::kDecal: p->append(SkRasterPipeline::decal_y, decal_ctx); break; |
617 | } |
618 | } |
619 | |
620 | void* ctx = gather; |
621 | switch (info.colorType()) { |
622 | case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); break; |
623 | case kA16_unorm_SkColorType: p->append(SkRasterPipeline::gather_a16, ctx); break; |
624 | case kA16_float_SkColorType: p->append(SkRasterPipeline::gather_af16, ctx); break; |
625 | case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, ctx); break; |
626 | case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, ctx); break; |
627 | case kR8G8_unorm_SkColorType: p->append(SkRasterPipeline::gather_rg88, ctx); break; |
628 | case kR16G16_unorm_SkColorType: p->append(SkRasterPipeline::gather_rg1616, ctx); break; |
629 | case kR16G16_float_SkColorType: p->append(SkRasterPipeline::gather_rgf16, ctx); break; |
630 | case kRGBA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); break; |
631 | case kRGBA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); break; |
632 | case kR16G16B16A16_unorm_SkColorType: |
633 | p->append(SkRasterPipeline::gather_16161616,ctx); break; |
634 | case kRGBA_F16Norm_SkColorType: |
635 | case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, ctx); break; |
636 | case kRGBA_F32_SkColorType: p->append(SkRasterPipeline::gather_f32, ctx); break; |
637 | |
638 | case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_a8, ctx); |
639 | p->append(SkRasterPipeline::alpha_to_gray ); break; |
640 | |
641 | case kRGB_888x_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); |
642 | p->append(SkRasterPipeline::force_opaque ); break; |
643 | |
644 | case kBGRA_1010102_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); |
645 | p->append(SkRasterPipeline::swap_rb ); break; |
646 | |
647 | case kRGB_101010x_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); |
648 | p->append(SkRasterPipeline::force_opaque ); break; |
649 | |
650 | case kBGR_101010x_SkColorType: p->append(SkRasterPipeline::gather_1010102, ctx); |
651 | p->append(SkRasterPipeline::force_opaque ); |
652 | p->append(SkRasterPipeline::swap_rb ); break; |
653 | |
654 | case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, ctx); |
655 | p->append(SkRasterPipeline::swap_rb ); break; |
656 | |
657 | case kUnknown_SkColorType: SkASSERT(false); |
658 | } |
659 | if (decal_ctx) { |
660 | p->append(SkRasterPipeline::check_decal_mask, decal_ctx); |
661 | } |
662 | }; |
663 | |
664 | auto append_misc = [&] { |
665 | SkColorSpace* cs = info.colorSpace(); |
666 | SkAlphaType at = info.alphaType(); |
667 | |
668 | // Color for A8 images comes from the paint. TODO: all alpha images? none? |
669 | if (info.colorType() == kAlpha_8_SkColorType) { |
670 | SkColor4f rgb = rec.fPaint.getColor4f(); |
671 | p->append_set_rgb(alloc, rgb); |
672 | |
673 | cs = sk_srgb_singleton(); |
674 | at = kUnpremul_SkAlphaType; |
675 | } |
676 | |
677 | // Bicubic filtering naturally produces out of range values on both sides of [0,1]. |
678 | if (quality == kHigh_SkFilterQuality) { |
679 | p->append(SkRasterPipeline::clamp_0); |
680 | p->append(at == kUnpremul_SkAlphaType || fClampAsIfUnpremul |
681 | ? SkRasterPipeline::clamp_1 |
682 | : SkRasterPipeline::clamp_a); |
683 | } |
684 | |
685 | // Transform color space and alpha type to match shader convention (dst CS, premul alpha). |
686 | alloc->make<SkColorSpaceXformSteps>(cs, at, |
687 | rec.fDstCS, kPremul_SkAlphaType) |
688 | ->apply(p); |
689 | |
690 | return true; |
691 | }; |
692 | |
693 | // Check for fast-path stages. |
694 | auto ct = info.colorType(); |
695 | if (true |
696 | && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) |
697 | && quality == kLow_SkFilterQuality |
698 | && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) { |
699 | |
700 | p->append(SkRasterPipeline::bilerp_clamp_8888, gather); |
701 | if (ct == kBGRA_8888_SkColorType) { |
702 | p->append(SkRasterPipeline::swap_rb); |
703 | } |
704 | return append_misc(); |
705 | } |
706 | if (true |
707 | && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) // TODO: all formats |
708 | && quality == kLow_SkFilterQuality |
709 | && fTileModeX != SkTileMode::kDecal // TODO decal too? |
710 | && fTileModeY != SkTileMode::kDecal) { |
711 | |
712 | auto ctx = alloc->make<SkRasterPipeline_SamplerCtx2>(); |
713 | *(SkRasterPipeline_GatherCtx*)(ctx) = *gather; |
714 | ctx->ct = ct; |
715 | ctx->tileX = fTileModeX; |
716 | ctx->tileY = fTileModeY; |
717 | ctx->invWidth = 1.0f / ctx->width; |
718 | ctx->invHeight = 1.0f / ctx->height; |
719 | p->append(SkRasterPipeline::bilinear, ctx); |
720 | return append_misc(); |
721 | } |
722 | if (true |
723 | && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) |
724 | && quality == kHigh_SkFilterQuality |
725 | && fTileModeX == SkTileMode::kClamp && fTileModeY == SkTileMode::kClamp) { |
726 | |
727 | p->append(SkRasterPipeline::bicubic_clamp_8888, gather); |
728 | if (ct == kBGRA_8888_SkColorType) { |
729 | p->append(SkRasterPipeline::swap_rb); |
730 | } |
731 | return append_misc(); |
732 | } |
733 | if (true |
734 | && (ct == kRGBA_8888_SkColorType || ct == kBGRA_8888_SkColorType) // TODO: all formats |
735 | && quality == kHigh_SkFilterQuality |
736 | && fTileModeX != SkTileMode::kDecal // TODO decal too? |
737 | && fTileModeY != SkTileMode::kDecal) { |
738 | |
739 | auto ctx = alloc->make<SkRasterPipeline_SamplerCtx2>(); |
740 | *(SkRasterPipeline_GatherCtx*)(ctx) = *gather; |
741 | ctx->ct = ct; |
742 | ctx->tileX = fTileModeX; |
743 | ctx->tileY = fTileModeY; |
744 | ctx->invWidth = 1.0f / ctx->width; |
745 | ctx->invHeight = 1.0f / ctx->height; |
746 | p->append(SkRasterPipeline::bicubic, ctx); |
747 | return append_misc(); |
748 | } |
749 | |
750 | SkRasterPipeline_SamplerCtx* sampler = nullptr; |
751 | if (quality != kNone_SkFilterQuality) { |
752 | sampler = alloc->make<SkRasterPipeline_SamplerCtx>(); |
753 | } |
754 | |
755 | auto sample = [&](SkRasterPipeline::StockStage setup_x, |
756 | SkRasterPipeline::StockStage setup_y) { |
757 | p->append(setup_x, sampler); |
758 | p->append(setup_y, sampler); |
759 | append_tiling_and_gather(); |
760 | p->append(SkRasterPipeline::accumulate, sampler); |
761 | }; |
762 | |
763 | if (quality == kNone_SkFilterQuality) { |
764 | append_tiling_and_gather(); |
765 | } else if (quality == kLow_SkFilterQuality) { |
766 | p->append(SkRasterPipeline::save_xy, sampler); |
767 | |
768 | sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny); |
769 | sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny); |
770 | sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py); |
771 | sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py); |
772 | |
773 | p->append(SkRasterPipeline::move_dst_src); |
774 | |
775 | } else { |
776 | SkASSERT(quality == kHigh_SkFilterQuality); |
777 | p->append(SkRasterPipeline::save_xy, sampler); |
778 | |
779 | sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y); |
780 | sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y); |
781 | sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y); |
782 | sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y); |
783 | |
784 | sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y); |
785 | sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y); |
786 | sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y); |
787 | sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y); |
788 | |
789 | sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y); |
790 | sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y); |
791 | sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y); |
792 | sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y); |
793 | |
794 | sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y); |
795 | sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y); |
796 | sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y); |
797 | sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y); |
798 | |
799 | p->append(SkRasterPipeline::move_dst_src); |
800 | } |
801 | |
802 | return append_misc(); |
803 | } |
804 | |
805 | bool SkImageShader::onAppendStages(const SkStageRec& rec) const { |
806 | return this->doStages(rec, nullptr); |
807 | } |
808 | |
809 | SkStageUpdater* SkImageShader::onAppendUpdatableStages(const SkStageRec& rec) const { |
810 | bool usePersp = rec.fMatrixProvider.localToDevice().hasPerspective(); |
811 | auto updater = rec.fAlloc->make<SkImageStageUpdater>(this, usePersp); |
812 | return this->doStages(rec, updater) ? updater : nullptr; |
813 | } |
814 | |
815 | enum class SamplingEnum { |
816 | kNearest, |
817 | kLinear, |
818 | kBicubic, |
819 | }; |
820 | |
821 | skvm::Color SkImageShader::onProgram(skvm::Builder* p, |
822 | skvm::Coord device, skvm::Coord origLocal, skvm::Color paint, |
823 | const SkMatrixProvider& matrices, const SkMatrix* localM, |
824 | SkFilterQuality paintQuality, const SkColorInfo& dst, |
825 | skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const { |
826 | SkMatrix baseInv; |
827 | if (!this->computeTotalInverse(matrices.localToDevice(), localM, &baseInv)) { |
828 | return {}; |
829 | } |
830 | baseInv.normalizePerspective(); |
831 | |
832 | const SkPixmap *upper = nullptr, |
833 | *lower = nullptr; |
834 | SkMatrix upperInv; |
835 | float lowerWeight = 0; |
836 | SamplingEnum sampling = (SamplingEnum)fFilterOptions.fSampling; |
837 | |
838 | auto post_scale = [&](SkISize level, const SkMatrix& base) { |
839 | return SkMatrix::Scale(SkIntToScalar(level.width()) / fImage->width(), |
840 | SkIntToScalar(level.height()) / fImage->height()) |
841 | * base; |
842 | }; |
843 | |
844 | if (fFilterEnum == kUseFilterOptions) { |
845 | auto* access = alloc->make<SkMipmapAccessor>(as_IB(fImage.get()), baseInv, |
846 | fFilterOptions.fMipmap); |
847 | upper = &access->level(); |
848 | upperInv = post_scale(upper->dimensions(), baseInv); |
849 | lowerWeight = access->lowerWeight(); |
850 | if (lowerWeight > 0) { |
851 | lower = &access->lowerLevel(); |
852 | } |
853 | } else if (fFilterEnum == kUseCubicResampler){ |
854 | auto* access = alloc->make<SkMipmapAccessor>(as_IB(fImage.get()), baseInv, |
855 | SkMipmapMode::kNone); |
856 | upper = &access->level(); |
857 | upperInv = post_scale(upper->dimensions(), baseInv); |
858 | sampling = SamplingEnum::kBicubic; |
859 | } else { |
860 | // Convert from the filter-quality enum to our working description: |
861 | // sampling : nearest, bilerp, bicubic |
862 | // miplevel(s) and associated matrices |
863 | // |
864 | SkFilterQuality quality = paintQuality; |
865 | if (fFilterEnum != kInheritFromPaint) { |
866 | quality = (SkFilterQuality)fFilterEnum; |
867 | } |
868 | |
869 | // We use RequestBitmap() to make sure our SkBitmapController::State lives in the alloc. |
870 | // This lets the SkVMBlitter hang on to this state and keep our image alive. |
871 | auto state = SkBitmapController::RequestBitmap(as_IB(fImage.get()), baseInv, quality, alloc); |
872 | if (!state) { |
873 | return {}; |
874 | } |
875 | upper = &state->pixmap(); |
876 | upperInv = state->invMatrix(); |
877 | |
878 | quality = state->quality(); |
879 | tweak_quality_and_inv_matrix(&quality, &upperInv); |
880 | switch (quality) { |
881 | case kNone_SkFilterQuality: sampling = SamplingEnum::kNearest; break; |
882 | case kLow_SkFilterQuality: sampling = SamplingEnum::kLinear; break; |
883 | case kMedium_SkFilterQuality: sampling = SamplingEnum::kLinear; break; |
884 | case kHigh_SkFilterQuality: sampling = SamplingEnum::kBicubic; break; |
885 | } |
886 | } |
887 | |
888 | skvm::Coord upperLocal = SkShaderBase::ApplyMatrix(p, upperInv, origLocal, uniforms); |
889 | |
890 | // All existing SkColorTypes pass these checks. We'd only fail here adding new ones. |
891 | skvm::PixelFormat unused; |
892 | if (true && !SkColorType_to_PixelFormat(upper->colorType(), &unused)) { |
893 | return {}; |
894 | } |
895 | if (lower && !SkColorType_to_PixelFormat(lower->colorType(), &unused)) { |
896 | return {}; |
897 | } |
898 | |
899 | // We can exploit image opacity to skip work unpacking alpha channels. |
900 | const bool input_is_opaque = SkAlphaTypeIsOpaque(upper->alphaType()) |
901 | || SkColorTypeIsAlwaysOpaque(upper->colorType()); |
902 | |
903 | // Each call to sample() will try to rewrite the same uniforms over and over, |
904 | // so remember where we start and reset back there each time. That way each |
905 | // sample() call uses the same uniform offsets. |
906 | |
907 | auto compute_clamp_limit = [&](float limit) { |
908 | // Subtract an ulp so the upper clamp limit excludes limit itself. |
909 | int bits; |
910 | memcpy(&bits, &limit, 4); |
911 | return p->uniformF(uniforms->push(bits-1)); |
912 | }; |
913 | |
914 | // Except in the simplest case (no mips, no filtering), we reference uniforms |
915 | // more than once. To avoid adding/registering them multiple times, we pre-load them |
916 | // into a struct (just to logically group them together), based on the "current" |
917 | // pixmap (level of a mipmap). |
918 | // |
919 | struct Uniforms { |
920 | skvm::F32 w, iw, i2w, |
921 | h, ih, i2h; |
922 | |
923 | skvm::F32 clamp_w, |
924 | clamp_h; |
925 | |
926 | skvm::Uniform addr; |
927 | skvm::I32 rowBytesAsPixels; |
928 | |
929 | skvm::PixelFormat pixelFormat; // not a uniform, but needed for each texel sample, |
930 | // so we store it here, since it is also dependent on |
931 | // the current pixmap (level). |
932 | }; |
933 | |
934 | auto setup_uniforms = [&](const SkPixmap& pm) -> Uniforms { |
935 | skvm::PixelFormat pixelFormat; |
936 | SkAssertResult(SkColorType_to_PixelFormat(pm.colorType(), &pixelFormat)); |
937 | return { |
938 | p->uniformF(uniforms->pushF( pm.width())), |
939 | p->uniformF(uniforms->pushF(1.0f/pm.width())), // iff tileX == kRepeat |
940 | p->uniformF(uniforms->pushF(0.5f/pm.width())), // iff tileX == kMirror |
941 | |
942 | p->uniformF(uniforms->pushF( pm.height())), |
943 | p->uniformF(uniforms->pushF(1.0f/pm.height())), // iff tileY == kRepeat |
944 | p->uniformF(uniforms->pushF(0.5f/pm.height())), // iff tileY == kMirror |
945 | |
946 | compute_clamp_limit(pm. width()), |
947 | compute_clamp_limit(pm.height()), |
948 | |
949 | uniforms->pushPtr(pm.addr()), |
950 | p->uniform32(uniforms->push(pm.rowBytesAsPixels())), |
951 | |
952 | pixelFormat, |
953 | }; |
954 | }; |
955 | |
956 | auto sample_texel = [&](const Uniforms& u, skvm::F32 sx, skvm::F32 sy) -> skvm::Color { |
957 | // repeat() and mirror() are written assuming they'll be followed by a [0,scale) clamp. |
958 | auto repeat = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I) { |
959 | return v - floor(v * I) * S; |
960 | }; |
961 | auto mirror = [&](skvm::F32 v, skvm::F32 S, skvm::F32 I2) { |
962 | // abs( (v-scale) - (2*scale)*floor((v-scale)*(0.5f/scale)) - scale ) |
963 | // {---A---} {------------------B------------------} |
964 | skvm::F32 A = v - S, |
965 | B = (S + S) * floor(A * I2); |
966 | return abs(A - B - S); |
967 | }; |
968 | switch (fTileModeX) { |
969 | case SkTileMode::kDecal: /* handled after gather */ break; |
970 | case SkTileMode::kClamp: /* we always clamp */ break; |
971 | case SkTileMode::kRepeat: sx = repeat(sx, u.w, u.iw); break; |
972 | case SkTileMode::kMirror: sx = mirror(sx, u.w, u.i2w); break; |
973 | } |
974 | switch (fTileModeY) { |
975 | case SkTileMode::kDecal: /* handled after gather */ break; |
976 | case SkTileMode::kClamp: /* we always clamp */ break; |
977 | case SkTileMode::kRepeat: sy = repeat(sy, u.h, u.ih); break; |
978 | case SkTileMode::kMirror: sy = mirror(sy, u.h, u.i2h); break; |
979 | } |
980 | |
981 | // Always clamp sample coordinates to [0,width), [0,height), both for memory |
982 | // safety and to handle the clamps still needed by kClamp, kRepeat, and kMirror. |
983 | skvm::F32 clamped_x = clamp(sx, 0, u.clamp_w), |
984 | clamped_y = clamp(sy, 0, u.clamp_h); |
985 | |
986 | // Load pixels from pm.addr()[(int)sx + (int)sy*stride]. |
987 | skvm::I32 index = trunc(clamped_x) + |
988 | trunc(clamped_y) * u.rowBytesAsPixels; |
989 | skvm::Color c = gather(u.pixelFormat, u.addr, index); |
990 | |
991 | // If we know the image is opaque, jump right to alpha = 1.0f, skipping work to unpack it. |
992 | if (input_is_opaque) { |
993 | c.a = p->splat(1.0f); |
994 | } |
995 | |
996 | // Mask away any pixels that we tried to sample outside the bounds in kDecal. |
997 | if (fTileModeX == SkTileMode::kDecal || fTileModeY == SkTileMode::kDecal) { |
998 | skvm::I32 mask = p->splat(~0); |
999 | if (fTileModeX == SkTileMode::kDecal) { mask &= (sx == clamped_x); } |
1000 | if (fTileModeY == SkTileMode::kDecal) { mask &= (sy == clamped_y); } |
1001 | c.r = bit_cast(p->bit_and(mask, bit_cast(c.r))); |
1002 | c.g = bit_cast(p->bit_and(mask, bit_cast(c.g))); |
1003 | c.b = bit_cast(p->bit_and(mask, bit_cast(c.b))); |
1004 | c.a = bit_cast(p->bit_and(mask, bit_cast(c.a))); |
1005 | // Notice that even if input_is_opaque, c.a might now be 0. |
1006 | } |
1007 | |
1008 | return c; |
1009 | }; |
1010 | |
1011 | auto sample_level = [&](const SkPixmap& pm, const SkMatrix& inv, skvm::Coord local) { |
1012 | const Uniforms u = setup_uniforms(pm); |
1013 | |
1014 | if (sampling == SamplingEnum::kNearest) { |
1015 | return sample_texel(u, local.x,local.y); |
1016 | } else if (sampling == SamplingEnum::kLinear) { |
1017 | // Our four sample points are the corners of a logical 1x1 pixel |
1018 | // box surrounding (x,y) at (0.5,0.5) off-center. |
1019 | skvm::F32 left = local.x - 0.5f, |
1020 | top = local.y - 0.5f, |
1021 | right = local.x + 0.5f, |
1022 | bottom = local.y + 0.5f; |
1023 | |
1024 | // The fractional parts of right and bottom are our lerp factors in x and y respectively. |
1025 | skvm::F32 fx = fract(right ), |
1026 | fy = fract(bottom); |
1027 | |
1028 | return lerp(lerp(sample_texel(u, left,top ), sample_texel(u, right,top ), fx), |
1029 | lerp(sample_texel(u, left,bottom), sample_texel(u, right,bottom), fx), fy); |
1030 | } else { |
1031 | SkASSERT(sampling == SamplingEnum::kBicubic); |
1032 | |
1033 | // All bicubic samples have the same fractional offset (fx,fy) from the center. |
1034 | // They're either the 16 corners of a 3x3 grid/ surrounding (x,y) at (0.5,0.5) off-center. |
1035 | skvm::F32 fx = fract(local.x + 0.5f), |
1036 | fy = fract(local.y + 0.5f); |
1037 | skvm::F32 wx[4], |
1038 | wy[4]; |
1039 | |
1040 | SkM44 weights = CubicResamplerMatrix(fCubic.B, fCubic.C); |
1041 | |
1042 | auto dot = [](const skvm::F32 a[], const skvm::F32 b[]) { |
1043 | return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; |
1044 | }; |
1045 | const skvm::F32 tmpx[] = { p->splat(1.0f), fx, fx*fx, fx*fx*fx }; |
1046 | const skvm::F32 tmpy[] = { p->splat(1.0f), fy, fy*fy, fy*fy*fy }; |
1047 | |
1048 | for (int row = 0; row < 4; ++row) { |
1049 | SkV4 r = weights.row(row); |
1050 | skvm::F32 ru[] = { |
1051 | p->uniformF(uniforms->pushF(r[0])), |
1052 | p->uniformF(uniforms->pushF(r[1])), |
1053 | p->uniformF(uniforms->pushF(r[2])), |
1054 | p->uniformF(uniforms->pushF(r[3])), |
1055 | }; |
1056 | wx[row] = dot(ru, tmpx); |
1057 | wy[row] = dot(ru, tmpy); |
1058 | } |
1059 | |
1060 | skvm::Color c; |
1061 | c.r = c.g = c.b = c.a = p->splat(0.0f); |
1062 | |
1063 | skvm::F32 sy = local.y - 1.5f; |
1064 | for (int j = 0; j < 4; j++, sy += 1.0f) { |
1065 | skvm::F32 sx = local.x - 1.5f; |
1066 | for (int i = 0; i < 4; i++, sx += 1.0f) { |
1067 | skvm::Color s = sample_texel(u, sx,sy); |
1068 | skvm::F32 w = wx[i] * wy[j]; |
1069 | |
1070 | c.r += s.r * w; |
1071 | c.g += s.g * w; |
1072 | c.b += s.b * w; |
1073 | c.a += s.a * w; |
1074 | } |
1075 | } |
1076 | return c; |
1077 | } |
1078 | }; |
1079 | |
1080 | skvm::Color c = sample_level(*upper, upperInv, upperLocal); |
1081 | if (lower) { |
1082 | auto lowerInv = post_scale(lower->dimensions(), baseInv); |
1083 | auto lowerLocal = SkShaderBase::ApplyMatrix(p, lowerInv, origLocal, uniforms); |
1084 | // lower * weight + upper * (1 - weight) |
1085 | c = lerp(c, |
1086 | sample_level(*lower, lowerInv, lowerLocal), |
1087 | p->uniformF(uniforms->pushF(lowerWeight))); |
1088 | } |
1089 | |
1090 | // If the input is opaque and we're not in decal mode, that means the output is too. |
1091 | // Forcing *a to 1.0 here will retroactively skip any work we did to interpolate sample alphas. |
1092 | if (input_is_opaque |
1093 | && fTileModeX != SkTileMode::kDecal |
1094 | && fTileModeY != SkTileMode::kDecal) { |
1095 | c.a = p->splat(1.0f); |
1096 | } |
1097 | |
1098 | // Alpha-only images get their color from the paint (already converted to dst color space). |
1099 | SkColorSpace* cs = upper->colorSpace(); |
1100 | SkAlphaType at = upper->alphaType(); |
1101 | if (SkColorTypeIsAlphaOnly(upper->colorType())) { |
1102 | c.r = paint.r; |
1103 | c.g = paint.g; |
1104 | c.b = paint.b; |
1105 | |
1106 | cs = dst.colorSpace(); |
1107 | at = kUnpremul_SkAlphaType; |
1108 | } |
1109 | |
1110 | if (sampling == SamplingEnum::kBicubic) { |
1111 | // Bicubic filtering naturally produces out of range values on both sides of [0,1]. |
1112 | c.a = clamp01(c.a); |
1113 | |
1114 | skvm::F32 limit = (at == kUnpremul_SkAlphaType || fClampAsIfUnpremul) |
1115 | ? p->splat(1.0f) |
1116 | : c.a; |
1117 | c.r = clamp(c.r, 0.0f, limit); |
1118 | c.g = clamp(c.g, 0.0f, limit); |
1119 | c.b = clamp(c.b, 0.0f, limit); |
1120 | } |
1121 | |
1122 | return SkColorSpaceXformSteps{cs,at, dst.colorSpace(),dst.alphaType()}.program(p, uniforms, c); |
1123 | } |
1124 | |