1 | /* |
2 | * Copyright 2016 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 "include/effects/SkArithmeticImageFilter.h" |
9 | |
10 | #include "include/core/SkCanvas.h" |
11 | #include "include/effects/SkXfermodeImageFilter.h" |
12 | #include "include/private/SkNx.h" |
13 | #include "src/core/SkImageFilter_Base.h" |
14 | #include "src/core/SkReadBuffer.h" |
15 | #include "src/core/SkSpecialImage.h" |
16 | #include "src/core/SkSpecialSurface.h" |
17 | #include "src/core/SkWriteBuffer.h" |
18 | #if SK_SUPPORT_GPU |
19 | #include "include/effects/SkRuntimeEffect.h" |
20 | #include "include/private/GrRecordingContext.h" |
21 | #include "src/gpu/GrClip.h" |
22 | #include "src/gpu/GrColorSpaceXform.h" |
23 | #include "src/gpu/GrRecordingContextPriv.h" |
24 | #include "src/gpu/GrRenderTargetContext.h" |
25 | #include "src/gpu/GrTextureProxy.h" |
26 | #include "src/gpu/SkGr.h" |
27 | #include "src/gpu/effects/GrSkSLFP.h" |
28 | #include "src/gpu/effects/generated/GrConstColorProcessor.h" |
29 | #include "src/gpu/glsl/GrGLSLFragmentProcessor.h" |
30 | #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
31 | #include "src/gpu/glsl/GrGLSLProgramDataManager.h" |
32 | #include "src/gpu/glsl/GrGLSLUniformHandler.h" |
33 | |
34 | GR_FP_SRC_STRING SKSL_ARITHMETIC_SRC = R"( |
35 | uniform float4 k; |
36 | in bool enforcePMColor; |
37 | in fragmentProcessor child; |
38 | |
39 | void main(float2 p, inout half4 color) { |
40 | half4 dst = sample(child, p); |
41 | color = saturate(half(k.x) * color * dst + half(k.y) * color + half(k.z) * dst + half(k.w)); |
42 | @if (enforcePMColor) { |
43 | color.rgb = min(color.rgb, color.a); |
44 | } |
45 | } |
46 | )" ; |
47 | #endif |
48 | |
49 | namespace { |
50 | |
51 | class ArithmeticImageFilterImpl final : public SkImageFilter_Base { |
52 | public: |
53 | ArithmeticImageFilterImpl(float k1, float k2, float k3, float k4, bool enforcePMColor, |
54 | sk_sp<SkImageFilter> inputs[2], const CropRect* cropRect) |
55 | : INHERITED(inputs, 2, cropRect), fInputs{k1, k2, k3, k4, enforcePMColor} {} |
56 | |
57 | protected: |
58 | sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override; |
59 | |
60 | SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, |
61 | MapDirection, const SkIRect* inputRect) const override; |
62 | |
63 | #if SK_SUPPORT_GPU |
64 | sk_sp<SkSpecialImage> filterImageGPU(const Context& ctx, |
65 | sk_sp<SkSpecialImage> background, |
66 | const SkIPoint& backgroundOffset, |
67 | sk_sp<SkSpecialImage> foreground, |
68 | const SkIPoint& foregroundOffset, |
69 | const SkIRect& bounds) const; |
70 | #endif |
71 | |
72 | void flatten(SkWriteBuffer& buffer) const override; |
73 | |
74 | void drawForeground(SkCanvas* canvas, SkSpecialImage*, const SkIRect&) const; |
75 | |
76 | private: |
77 | friend void SkArithmeticImageFilter::RegisterFlattenables(); |
78 | SK_FLATTENABLE_HOOKS(ArithmeticImageFilterImpl) |
79 | |
80 | bool affectsTransparentBlack() const override { return !SkScalarNearlyZero(fInputs.fK[3]); } |
81 | |
82 | ArithmeticFPInputs fInputs; |
83 | |
84 | typedef SkImageFilter_Base INHERITED; |
85 | }; |
86 | |
87 | }; // end namespace |
88 | |
89 | sk_sp<SkImageFilter> SkArithmeticImageFilter::Make(float k1, float k2, float k3, float k4, |
90 | bool enforcePMColor, |
91 | sk_sp<SkImageFilter> background, |
92 | sk_sp<SkImageFilter> foreground, |
93 | const SkImageFilter::CropRect* crop) { |
94 | if (!SkScalarIsFinite(k1) || !SkScalarIsFinite(k2) || !SkScalarIsFinite(k3) || |
95 | !SkScalarIsFinite(k4)) { |
96 | return nullptr; |
97 | } |
98 | |
99 | // are we nearly some other "std" mode? |
100 | int mode = -1; // illegal mode |
101 | if (SkScalarNearlyZero(k1) && SkScalarNearlyEqual(k2, SK_Scalar1) && SkScalarNearlyZero(k3) && |
102 | SkScalarNearlyZero(k4)) { |
103 | mode = (int)SkBlendMode::kSrc; |
104 | } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && |
105 | SkScalarNearlyEqual(k3, SK_Scalar1) && SkScalarNearlyZero(k4)) { |
106 | mode = (int)SkBlendMode::kDst; |
107 | } else if (SkScalarNearlyZero(k1) && SkScalarNearlyZero(k2) && SkScalarNearlyZero(k3) && |
108 | SkScalarNearlyZero(k4)) { |
109 | mode = (int)SkBlendMode::kClear; |
110 | } |
111 | if (mode >= 0) { |
112 | return SkXfermodeImageFilter::Make((SkBlendMode)mode, std::move(background), |
113 | std::move(foreground), crop); |
114 | } |
115 | |
116 | sk_sp<SkImageFilter> inputs[2] = {std::move(background), std::move(foreground)}; |
117 | return sk_sp<SkImageFilter>( |
118 | new ArithmeticImageFilterImpl(k1, k2, k3, k4, enforcePMColor, inputs, crop)); |
119 | } |
120 | |
121 | void SkArithmeticImageFilter::RegisterFlattenables() { |
122 | SK_REGISTER_FLATTENABLE(ArithmeticImageFilterImpl); |
123 | } |
124 | |
125 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
126 | |
127 | sk_sp<SkFlattenable> ArithmeticImageFilterImpl::CreateProc(SkReadBuffer& buffer) { |
128 | SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2); |
129 | float k[4]; |
130 | for (int i = 0; i < 4; ++i) { |
131 | k[i] = buffer.readScalar(); |
132 | } |
133 | const bool enforcePMColor = buffer.readBool(); |
134 | if (!buffer.isValid()) { |
135 | return nullptr; |
136 | } |
137 | return SkArithmeticImageFilter::Make(k[0], k[1], k[2], k[3], enforcePMColor, common.getInput(0), |
138 | common.getInput(1), &common.cropRect()); |
139 | } |
140 | |
141 | void ArithmeticImageFilterImpl::flatten(SkWriteBuffer& buffer) const { |
142 | this->INHERITED::flatten(buffer); |
143 | for (int i = 0; i < 4; ++i) { |
144 | buffer.writeScalar(fInputs.fK[i]); |
145 | } |
146 | buffer.writeBool(fInputs.fEnforcePMColor); |
147 | } |
148 | |
149 | static Sk4f pin(float min, const Sk4f& val, float max) { |
150 | return Sk4f::Max(min, Sk4f::Min(val, max)); |
151 | } |
152 | |
153 | template <bool EnforcePMColor> |
154 | void arith_span(const float k[], SkPMColor dst[], const SkPMColor src[], int count) { |
155 | const Sk4f k1 = k[0] * (1/255.0f), |
156 | k2 = k[1], |
157 | k3 = k[2], |
158 | k4 = k[3] * 255.0f + 0.5f; |
159 | |
160 | for (int i = 0; i < count; i++) { |
161 | Sk4f s = SkNx_cast<float>(Sk4b::Load(src+i)), |
162 | d = SkNx_cast<float>(Sk4b::Load(dst+i)), |
163 | r = pin(0, k1*s*d + k2*s + k3*d + k4, 255); |
164 | if (EnforcePMColor) { |
165 | Sk4f a = SkNx_shuffle<3,3,3,3>(r); |
166 | r = Sk4f::Min(a, r); |
167 | } |
168 | SkNx_cast<uint8_t>(r).store(dst+i); |
169 | } |
170 | } |
171 | |
172 | // apply mode to src==transparent (0) |
173 | template<bool EnforcePMColor> void arith_transparent(const float k[], SkPMColor dst[], int count) { |
174 | const Sk4f k3 = k[2], |
175 | k4 = k[3] * 255.0f + 0.5f; |
176 | |
177 | for (int i = 0; i < count; i++) { |
178 | Sk4f d = SkNx_cast<float>(Sk4b::Load(dst+i)), |
179 | r = pin(0, k3*d + k4, 255); |
180 | if (EnforcePMColor) { |
181 | Sk4f a = SkNx_shuffle<3,3,3,3>(r); |
182 | r = Sk4f::Min(a, r); |
183 | } |
184 | SkNx_cast<uint8_t>(r).store(dst+i); |
185 | } |
186 | } |
187 | |
188 | static bool intersect(SkPixmap* dst, SkPixmap* src, int srcDx, int srcDy) { |
189 | SkIRect dstR = SkIRect::MakeWH(dst->width(), dst->height()); |
190 | SkIRect srcR = SkIRect::MakeXYWH(srcDx, srcDy, src->width(), src->height()); |
191 | SkIRect sect; |
192 | if (!sect.intersect(dstR, srcR)) { |
193 | return false; |
194 | } |
195 | *dst = SkPixmap(dst->info().makeDimensions(sect.size()), |
196 | dst->addr(sect.fLeft, sect.fTop), |
197 | dst->rowBytes()); |
198 | *src = SkPixmap(src->info().makeDimensions(sect.size()), |
199 | src->addr(std::max(0, -srcDx), std::max(0, -srcDy)), |
200 | src->rowBytes()); |
201 | return true; |
202 | } |
203 | |
204 | sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::onFilterImage(const Context& ctx, |
205 | SkIPoint* offset) const { |
206 | SkIPoint backgroundOffset = SkIPoint::Make(0, 0); |
207 | sk_sp<SkSpecialImage> background(this->filterInput(0, ctx, &backgroundOffset)); |
208 | |
209 | SkIPoint foregroundOffset = SkIPoint::Make(0, 0); |
210 | sk_sp<SkSpecialImage> foreground(this->filterInput(1, ctx, &foregroundOffset)); |
211 | |
212 | SkIRect foregroundBounds = SkIRect::MakeEmpty(); |
213 | if (foreground) { |
214 | foregroundBounds = SkIRect::MakeXYWH(foregroundOffset.x(), foregroundOffset.y(), |
215 | foreground->width(), foreground->height()); |
216 | } |
217 | |
218 | SkIRect srcBounds = SkIRect::MakeEmpty(); |
219 | if (background) { |
220 | srcBounds = SkIRect::MakeXYWH(backgroundOffset.x(), backgroundOffset.y(), |
221 | background->width(), background->height()); |
222 | } |
223 | |
224 | srcBounds.join(foregroundBounds); |
225 | if (srcBounds.isEmpty()) { |
226 | return nullptr; |
227 | } |
228 | |
229 | SkIRect bounds; |
230 | if (!this->applyCropRect(ctx, srcBounds, &bounds)) { |
231 | return nullptr; |
232 | } |
233 | |
234 | offset->fX = bounds.left(); |
235 | offset->fY = bounds.top(); |
236 | |
237 | #if SK_SUPPORT_GPU |
238 | if (ctx.gpuBacked()) { |
239 | return this->filterImageGPU(ctx, background, backgroundOffset, foreground, |
240 | foregroundOffset, bounds); |
241 | } |
242 | #endif |
243 | |
244 | sk_sp<SkSpecialSurface> surf(ctx.makeSurface(bounds.size())); |
245 | if (!surf) { |
246 | return nullptr; |
247 | } |
248 | |
249 | SkCanvas* canvas = surf->getCanvas(); |
250 | SkASSERT(canvas); |
251 | |
252 | canvas->clear(0x0); // can't count on background to fully clear the background |
253 | canvas->translate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); |
254 | |
255 | if (background) { |
256 | SkPaint paint; |
257 | paint.setBlendMode(SkBlendMode::kSrc); |
258 | background->draw(canvas, SkIntToScalar(backgroundOffset.fX), |
259 | SkIntToScalar(backgroundOffset.fY), &paint); |
260 | } |
261 | |
262 | this->drawForeground(canvas, foreground.get(), foregroundBounds); |
263 | |
264 | return surf->makeImageSnapshot(); |
265 | } |
266 | |
267 | SkIRect ArithmeticImageFilterImpl::onFilterBounds(const SkIRect& src, |
268 | const SkMatrix& ctm, |
269 | MapDirection dir, |
270 | const SkIRect* inputRect) const { |
271 | if (kReverse_MapDirection == dir) { |
272 | return INHERITED::onFilterBounds(src, ctm, dir, inputRect); |
273 | } |
274 | |
275 | SkASSERT(2 == this->countInputs()); |
276 | |
277 | // result(i1,i2) = k1*i1*i2 + k2*i1 + k3*i2 + k4 |
278 | // Note that background (getInput(0)) is i2, and foreground (getInput(1)) is i1. |
279 | auto i2 = this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, dir, nullptr) : src; |
280 | auto i1 = this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, dir, nullptr) : src; |
281 | |
282 | // Arithmetic with non-zero k4 may influence the complete filter primitive |
283 | // region. [k4 > 0 => result(0,0) = k4 => result(i1,i2) >= k4] |
284 | if (!SkScalarNearlyZero(fInputs.fK[3])) { |
285 | i1.join(i2); |
286 | return i1; |
287 | } |
288 | |
289 | // If both K2 or K3 are non-zero, both i1 and i2 appear. |
290 | if (!SkScalarNearlyZero(fInputs.fK[1]) && !SkScalarNearlyZero(fInputs.fK[2])) { |
291 | i1.join(i2); |
292 | return i1; |
293 | } |
294 | |
295 | // If k2 is non-zero, output can be produced whenever i1 is non-transparent. |
296 | // [k3 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k2*i1 = (k1*i2 + k2)*i1] |
297 | if (!SkScalarNearlyZero(fInputs.fK[1])) { |
298 | return i1; |
299 | } |
300 | |
301 | // If k3 is non-zero, output can be produced whenever i2 is non-transparent. |
302 | // [k2 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k3*i2 = (k1*i1 + k3)*i2] |
303 | if (!SkScalarNearlyZero(fInputs.fK[2])) { |
304 | return i2; |
305 | } |
306 | |
307 | // If just k1 is non-zero, output will only be produce where both inputs |
308 | // are non-transparent. Use intersection. |
309 | // [k1 > 0 and k2 = k3 = k4 = 0 => result(i1,i2) = k1*i1*i2] |
310 | if (!SkScalarNearlyZero(fInputs.fK[0])) { |
311 | if (!i1.intersect(i2)) { |
312 | return SkIRect::MakeEmpty(); |
313 | } |
314 | return i1; |
315 | } |
316 | |
317 | // [k1 = k2 = k3 = k4 = 0 => result(i1,i2) = 0] |
318 | return SkIRect::MakeEmpty(); |
319 | } |
320 | |
321 | #if SK_SUPPORT_GPU |
322 | |
323 | sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU( |
324 | const Context& ctx, |
325 | sk_sp<SkSpecialImage> background, |
326 | const SkIPoint& backgroundOffset, |
327 | sk_sp<SkSpecialImage> foreground, |
328 | const SkIPoint& foregroundOffset, |
329 | const SkIRect& bounds) const { |
330 | SkASSERT(ctx.gpuBacked()); |
331 | |
332 | auto context = ctx.getContext(); |
333 | |
334 | GrSurfaceProxyView backgroundView, foregroundView; |
335 | |
336 | GrProtected isProtected = GrProtected::kNo; |
337 | if (background) { |
338 | backgroundView = background->view(context); |
339 | SkASSERT(backgroundView.proxy()); |
340 | isProtected = backgroundView.proxy()->isProtected(); |
341 | } |
342 | |
343 | if (foreground) { |
344 | foregroundView = foreground->view(context); |
345 | SkASSERT(foregroundView.proxy()); |
346 | isProtected = foregroundView.proxy()->isProtected(); |
347 | } |
348 | |
349 | GrPaint paint; |
350 | std::unique_ptr<GrFragmentProcessor> bgFP; |
351 | const auto& caps = *ctx.getContext()->priv().caps(); |
352 | GrSamplerState sampler(GrSamplerState::WrapMode::kClampToBorder, |
353 | GrSamplerState::Filter::kNearest); |
354 | |
355 | if (background) { |
356 | SkRect bgSubset = SkRect::Make(background->subset()); |
357 | SkMatrix backgroundMatrix = SkMatrix::MakeTrans( |
358 | SkIntToScalar(bgSubset.left() - backgroundOffset.fX), |
359 | SkIntToScalar(bgSubset.top() - backgroundOffset.fY)); |
360 | bgFP = GrTextureEffect::MakeSubset(std::move(backgroundView), background->alphaType(), |
361 | backgroundMatrix, sampler, bgSubset, caps); |
362 | bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(), |
363 | background->alphaType(), |
364 | ctx.colorSpace()); |
365 | } else { |
366 | bgFP = GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT, |
367 | GrConstColorProcessor::InputMode::kIgnore); |
368 | } |
369 | |
370 | if (foreground) { |
371 | SkRect fgSubset = SkRect::Make(foreground->subset()); |
372 | SkMatrix foregroundMatrix = SkMatrix::MakeTrans( |
373 | SkIntToScalar(fgSubset.left() - foregroundOffset.fX), |
374 | SkIntToScalar(fgSubset.top() - foregroundOffset.fY)); |
375 | auto fgFP = GrTextureEffect::MakeSubset(std::move(foregroundView), foreground->alphaType(), |
376 | foregroundMatrix, sampler, fgSubset, caps); |
377 | fgFP = GrColorSpaceXformEffect::Make(std::move(fgFP), |
378 | foreground->getColorSpace(), |
379 | foreground->alphaType(), |
380 | ctx.colorSpace()); |
381 | paint.addColorFragmentProcessor(std::move(fgFP)); |
382 | |
383 | static auto effect = std::get<0>(SkRuntimeEffect::Make(SkString(SKSL_ARITHMETIC_SRC))); |
384 | SkASSERT(effect->inputSize() == sizeof(fInputs)); |
385 | std::unique_ptr<GrFragmentProcessor> xferFP = GrSkSLFP::Make( |
386 | context, effect, "Arithmetic" , SkData::MakeWithCopy(&fInputs, sizeof(fInputs))); |
387 | if (xferFP) { |
388 | ((GrSkSLFP&) *xferFP).addChild(std::move(bgFP)); |
389 | paint.addColorFragmentProcessor(std::move(xferFP)); |
390 | } |
391 | } else { |
392 | paint.addColorFragmentProcessor(std::move(bgFP)); |
393 | } |
394 | |
395 | paint.setPorterDuffXPFactory(SkBlendMode::kSrc); |
396 | |
397 | auto renderTargetContext = GrRenderTargetContext::Make( |
398 | context, ctx.grColorType(), ctx.refColorSpace(), SkBackingFit::kApprox, bounds.size(), |
399 | 1, GrMipMapped::kNo, isProtected, kBottomLeft_GrSurfaceOrigin); |
400 | if (!renderTargetContext) { |
401 | return nullptr; |
402 | } |
403 | |
404 | SkMatrix matrix; |
405 | matrix.setTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top())); |
406 | renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, |
407 | SkRect::Make(bounds)); |
408 | |
409 | return SkSpecialImage::MakeDeferredFromGpu(context, |
410 | SkIRect::MakeWH(bounds.width(), bounds.height()), |
411 | kNeedNewImageUniqueID_SpecialImage, |
412 | renderTargetContext->readSurfaceView(), |
413 | renderTargetContext->colorInfo().colorType(), |
414 | renderTargetContext->colorInfo().refColorSpace()); |
415 | } |
416 | #endif |
417 | |
418 | void ArithmeticImageFilterImpl::drawForeground(SkCanvas* canvas, SkSpecialImage* img, |
419 | const SkIRect& fgBounds) const { |
420 | SkPixmap dst; |
421 | if (!canvas->peekPixels(&dst)) { |
422 | return; |
423 | } |
424 | |
425 | const SkMatrix& ctm = canvas->getTotalMatrix(); |
426 | SkASSERT(ctm.getType() <= SkMatrix::kTranslate_Mask); |
427 | const int dx = SkScalarRoundToInt(ctm.getTranslateX()); |
428 | const int dy = SkScalarRoundToInt(ctm.getTranslateY()); |
429 | // be sure to perform this offset using SkIRect, since it saturates to avoid overflows |
430 | const SkIRect fgoffset = fgBounds.makeOffset(dx, dy); |
431 | |
432 | if (img) { |
433 | SkBitmap srcBM; |
434 | SkPixmap src; |
435 | if (!img->getROPixels(&srcBM)) { |
436 | return; |
437 | } |
438 | if (!srcBM.peekPixels(&src)) { |
439 | return; |
440 | } |
441 | |
442 | auto proc = fInputs.fEnforcePMColor ? arith_span<true> : arith_span<false>; |
443 | SkPixmap tmpDst = dst; |
444 | if (intersect(&tmpDst, &src, fgoffset.fLeft, fgoffset.fTop)) { |
445 | for (int y = 0; y < tmpDst.height(); ++y) { |
446 | proc(fInputs.fK, tmpDst.writable_addr32(0, y), src.addr32(0, y), tmpDst.width()); |
447 | } |
448 | } |
449 | } |
450 | |
451 | // Now apply the mode with transparent-color to the outside of the fg image |
452 | SkRegion outside(SkIRect::MakeWH(dst.width(), dst.height())); |
453 | outside.op(fgoffset, SkRegion::kDifference_Op); |
454 | auto proc = fInputs.fEnforcePMColor ? arith_transparent<true> : arith_transparent<false>; |
455 | for (SkRegion::Iterator iter(outside); !iter.done(); iter.next()) { |
456 | const SkIRect r = iter.rect(); |
457 | for (int y = r.fTop; y < r.fBottom; ++y) { |
458 | proc(fInputs.fK, dst.writable_addr32(r.fLeft, y), r.width()); |
459 | } |
460 | } |
461 | } |
462 | |