1 /*
2  * Copyright 2013 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/SkTileImageFilter.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkImage.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkShader.h"
15 #include "include/core/SkSurface.h"
16 #include "include/effects/SkOffsetImageFilter.h"
17 #include "src/core/SkImageFilter_Base.h"
18 #include "src/core/SkReadBuffer.h"
19 #include "src/core/SkSpecialImage.h"
20 #include "src/core/SkSpecialSurface.h"
21 #include "src/core/SkValidationUtils.h"
22 #include "src/core/SkWriteBuffer.h"
23 
24 namespace {
25 
26 class SkTileImageFilterImpl final : public SkImageFilter_Base {
27 public:
SkTileImageFilterImpl(const SkRect & srcRect,const SkRect & dstRect,sk_sp<SkImageFilter> input)28     SkTileImageFilterImpl(const SkRect& srcRect, const SkRect& dstRect, sk_sp<SkImageFilter> input)
29             : INHERITED(&input, 1, nullptr)
30             , fSrcRect(srcRect)
31             , fDstRect(dstRect) {}
32 
33     SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
34                            MapDirection, const SkIRect* inputRect) const override;
35     SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
36                                MapDirection, const SkIRect* inputRect) const override;
37     SkRect computeFastBounds(const SkRect& src) const override;
38 
39 protected:
40     void flatten(SkWriteBuffer& buffer) const override;
41 
42     sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
43 
44 private:
45     friend void SkTileImageFilter::RegisterFlattenables();
46     SK_FLATTENABLE_HOOKS(SkTileImageFilterImpl)
47 
48     SkRect fSrcRect;
49     SkRect fDstRect;
50 
51     typedef SkImageFilter_Base INHERITED;
52 };
53 
54 } // end namespace
55 
Make(const SkRect & srcRect,const SkRect & dstRect,sk_sp<SkImageFilter> input)56 sk_sp<SkImageFilter> SkTileImageFilter::Make(const SkRect& srcRect, const SkRect& dstRect,
57                                              sk_sp<SkImageFilter> input) {
58     if (!SkIsValidRect(srcRect) || !SkIsValidRect(dstRect)) {
59         return nullptr;
60     }
61     if (srcRect.width() == dstRect.width() && srcRect.height() == dstRect.height()) {
62         SkRect ir = dstRect;
63         if (!ir.intersect(srcRect)) {
64             return input;
65         }
66         SkImageFilter::CropRect cropRect(ir);
67         return SkOffsetImageFilter::Make(dstRect.x() - srcRect.x(),
68                                          dstRect.y() - srcRect.y(),
69                                          std::move(input),
70                                          &cropRect);
71     }
72     return sk_sp<SkImageFilter>(new SkTileImageFilterImpl(srcRect, dstRect, std::move(input)));
73 }
74 
RegisterFlattenables()75 void SkTileImageFilter::RegisterFlattenables() {
76     SK_REGISTER_FLATTENABLE(SkTileImageFilterImpl);
77     // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
78     SkFlattenable::Register("SkTileImageFilter", SkTileImageFilterImpl::CreateProc);
79 }
80 
81 ///////////////////////////////////////////////////////////////////////////////////////////////////
82 
CreateProc(SkReadBuffer & buffer)83 sk_sp<SkFlattenable> SkTileImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
84     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
85     SkRect src, dst;
86     buffer.readRect(&src);
87     buffer.readRect(&dst);
88     return SkTileImageFilter::Make(src, dst, common.getInput(0));
89 }
90 
flatten(SkWriteBuffer & buffer) const91 void SkTileImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
92     this->INHERITED::flatten(buffer);
93     buffer.writeRect(fSrcRect);
94     buffer.writeRect(fDstRect);
95 }
96 
onFilterImage(const Context & ctx,SkIPoint * offset) const97 sk_sp<SkSpecialImage> SkTileImageFilterImpl::onFilterImage(const Context& ctx,
98                                                            SkIPoint* offset) const {
99     SkIPoint inputOffset = SkIPoint::Make(0, 0);
100     sk_sp<SkSpecialImage> input(this->filterInput(0, ctx, &inputOffset));
101     if (!input) {
102         return nullptr;
103     }
104 
105     SkRect dstRect;
106     ctx.ctm().mapRect(&dstRect, fDstRect);
107     if (!dstRect.intersect(SkRect::Make(ctx.clipBounds()))) {
108         return nullptr;
109     }
110 
111     const SkIRect dstIRect = dstRect.roundOut();
112     if (!fSrcRect.width() || !fSrcRect.height() || !dstIRect.width() || !dstIRect.height()) {
113         return nullptr;
114     }
115 
116     SkRect srcRect;
117     ctx.ctm().mapRect(&srcRect, fSrcRect);
118     SkIRect srcIRect;
119     srcRect.roundOut(&srcIRect);
120     srcIRect.offset(-inputOffset);
121     const SkIRect inputBounds = SkIRect::MakeWH(input->width(), input->height());
122 
123     if (!SkIRect::Intersects(srcIRect, inputBounds)) {
124         return nullptr;
125     }
126 
127     // We create an SkImage here b.c. it needs to be a tight fit for the tiling
128     sk_sp<SkImage> subset;
129     if (inputBounds.contains(srcIRect)) {
130         subset = input->asImage(&srcIRect);
131     } else {
132         sk_sp<SkSurface> surf(input->makeTightSurface(ctx.colorType(), ctx.colorSpace(),
133                                                       srcIRect.size()));
134         if (!surf) {
135             return nullptr;
136         }
137 
138         SkCanvas* canvas = surf->getCanvas();
139         SkASSERT(canvas);
140 
141         SkPaint paint;
142         paint.setBlendMode(SkBlendMode::kSrc);
143 
144         input->draw(canvas,
145                     SkIntToScalar(inputOffset.x()), SkIntToScalar(inputOffset.y()),
146                     &paint);
147 
148         subset = surf->makeImageSnapshot();
149     }
150     if (!subset) {
151         return nullptr;
152     }
153     SkASSERT(subset->width() == srcIRect.width());
154     SkASSERT(subset->height() == srcIRect.height());
155 
156     sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstIRect.size()));
157     if (!surf) {
158         return nullptr;
159     }
160 
161     SkCanvas* canvas = surf->getCanvas();
162     SkASSERT(canvas);
163 
164     SkPaint paint;
165     paint.setBlendMode(SkBlendMode::kSrc);
166     paint.setShader(subset->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
167     canvas->translate(-dstRect.fLeft, -dstRect.fTop);
168     canvas->drawRect(dstRect, paint);
169     offset->fX = dstIRect.fLeft;
170     offset->fY = dstIRect.fTop;
171     return surf->makeImageSnapshot();
172 }
173 
onFilterNodeBounds(const SkIRect & src,const SkMatrix & ctm,MapDirection dir,const SkIRect * inputRect) const174 SkIRect SkTileImageFilterImpl::onFilterNodeBounds(
175         const SkIRect& src, const SkMatrix& ctm, MapDirection dir, const SkIRect* inputRect) const {
176     SkRect rect = kReverse_MapDirection == dir ? fSrcRect : fDstRect;
177     ctm.mapRect(&rect);
178     return rect.roundOut();
179 }
180 
onFilterBounds(const SkIRect & src,const SkMatrix &,MapDirection,const SkIRect * inputRect) const181 SkIRect SkTileImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMatrix&,
182                                               MapDirection, const SkIRect* inputRect) const {
183     // Don't recurse into inputs.
184     return src;
185 }
186 
computeFastBounds(const SkRect & src) const187 SkRect SkTileImageFilterImpl::computeFastBounds(const SkRect& src) const {
188     return fDstRect;
189 }
190 
191