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 "SkAlphaThresholdFilter.h"
9
10#include "SkBitmap.h"
11#include "SkReadBuffer.h"
12#include "SkSpecialImage.h"
13#include "SkWriteBuffer.h"
14#include "SkRegion.h"
15
16#if SK_SUPPORT_GPU
17#include "GrAlphaThresholdFragmentProcessor.h"
18#include "GrContext.h"
19#include "GrFixedClip.h"
20#include "GrRenderTargetContext.h"
21#include "GrTextureProxy.h"
22#endif
23
24class SK_API SkAlphaThresholdFilterImpl : public SkImageFilter {
25public:
26    SkAlphaThresholdFilterImpl(const SkRegion& region, SkScalar innerThreshold,
27                               SkScalar outerThreshold, sk_sp<SkImageFilter> input,
28                               const CropRect* cropRect = nullptr);
29
30    SK_TO_STRING_OVERRIDE()
31    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkAlphaThresholdFilterImpl)
32    friend void SkAlphaThresholdFilter::InitializeFlattenables();
33
34protected:
35    void flatten(SkWriteBuffer&) const override;
36
37    sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&,
38                                        SkIPoint* offset) const override;
39
40#if SK_SUPPORT_GPU
41    sk_sp<GrTextureProxy> createMaskTexture(GrContext*,
42                                            const SkMatrix&,
43                                            const SkIRect& bounds) const;
44#endif
45
46private:
47    SkRegion fRegion;
48    SkScalar fInnerThreshold;
49    SkScalar fOuterThreshold;
50    typedef SkImageFilter INHERITED;
51};
52
53SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkAlphaThresholdFilter)
54    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAlphaThresholdFilterImpl)
55SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
56
57static SkScalar pin_0_1(SkScalar x) {
58    return SkMinScalar(SkMaxScalar(x, 0), 1);
59}
60
61sk_sp<SkImageFilter> SkAlphaThresholdFilter::Make(const SkRegion& region,
62                                                  SkScalar innerThreshold,
63                                                  SkScalar outerThreshold,
64                                                  sk_sp<SkImageFilter> input,
65                                                  const SkImageFilter::CropRect* cropRect) {
66    innerThreshold = pin_0_1(innerThreshold);
67    outerThreshold = pin_0_1(outerThreshold);
68    if (!SkScalarIsFinite(innerThreshold) || !SkScalarIsFinite(outerThreshold)) {
69        return nullptr;
70    }
71    return sk_sp<SkImageFilter>(new SkAlphaThresholdFilterImpl(region, innerThreshold,
72                                                               outerThreshold,
73                                                               std::move(input),
74                                                               cropRect));
75}
76
77sk_sp<SkFlattenable> SkAlphaThresholdFilterImpl::CreateProc(SkReadBuffer& buffer) {
78    SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
79    SkScalar inner = buffer.readScalar();
80    SkScalar outer = buffer.readScalar();
81    SkRegion rgn;
82    buffer.readRegion(&rgn);
83    return SkAlphaThresholdFilter::Make(rgn, inner, outer, common.getInput(0),
84                                        &common.cropRect());
85}
86
87SkAlphaThresholdFilterImpl::SkAlphaThresholdFilterImpl(const SkRegion& region,
88                                                       SkScalar innerThreshold,
89                                                       SkScalar outerThreshold,
90                                                       sk_sp<SkImageFilter> input,
91                                                       const CropRect* cropRect)
92    : INHERITED(&input, 1, cropRect)
93    , fRegion(region)
94    , fInnerThreshold(innerThreshold)
95    , fOuterThreshold(outerThreshold) {
96}
97
98#if SK_SUPPORT_GPU
99sk_sp<GrTextureProxy> SkAlphaThresholdFilterImpl::createMaskTexture(GrContext* context,
100                                                                    const SkMatrix& inMatrix,
101                                                                    const SkIRect& bounds) const {
102
103    sk_sp<GrRenderTargetContext> rtContext(context->makeDeferredRenderTargetContextWithFallback(
104        SkBackingFit::kApprox, bounds.width(), bounds.height(), kAlpha_8_GrPixelConfig, nullptr));
105    if (!rtContext) {
106        return nullptr;
107    }
108
109    GrPaint paint;
110    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
111    SkRegion::Iterator iter(fRegion);
112    rtContext->clear(nullptr, 0x0, true);
113
114    GrFixedClip clip(SkIRect::MakeWH(bounds.width(), bounds.height()));
115    while (!iter.done()) {
116        SkRect rect = SkRect::Make(iter.rect());
117        rtContext->drawRect(clip, std::move(paint), GrAA::kNo, inMatrix, rect);
118        iter.next();
119    }
120
121    return rtContext->asTextureProxyRef();
122}
123#endif
124
125void SkAlphaThresholdFilterImpl::flatten(SkWriteBuffer& buffer) const {
126    this->INHERITED::flatten(buffer);
127    buffer.writeScalar(fInnerThreshold);
128    buffer.writeScalar(fOuterThreshold);
129    buffer.writeRegion(fRegion);
130}
131
132sk_sp<SkSpecialImage> SkAlphaThresholdFilterImpl::onFilterImage(SkSpecialImage* source,
133                                                                const Context& ctx,
134                                                                SkIPoint* offset) const {
135    SkIPoint inputOffset = SkIPoint::Make(0, 0);
136    sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset));
137    if (!input) {
138        return nullptr;
139    }
140
141    const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(),
142                                                  input->width(), input->height());
143
144    SkIRect bounds;
145    if (!this->applyCropRect(ctx, inputBounds, &bounds)) {
146        return nullptr;
147    }
148
149#if SK_SUPPORT_GPU
150    if (source->isTextureBacked()) {
151        GrContext* context = source->getContext();
152
153        sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context));
154        SkASSERT(inputProxy);
155
156        offset->fX = bounds.left();
157        offset->fY = bounds.top();
158
159        bounds.offset(-inputOffset);
160
161        SkMatrix matrix(ctx.ctm());
162        matrix.postTranslate(SkIntToScalar(-bounds.left()), SkIntToScalar(-bounds.top()));
163
164        sk_sp<GrTextureProxy> maskProxy(this->createMaskTexture(context, matrix, bounds));
165        if (!maskProxy) {
166            return nullptr;
167        }
168
169        const OutputProperties& outProps = ctx.outputProperties();
170        sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(input->getColorSpace(),
171                                                                           outProps.colorSpace());
172
173        sk_sp<GrFragmentProcessor> fp(GrAlphaThresholdFragmentProcessor::Make(
174                                            context->resourceProvider(),
175                                            std::move(inputProxy),
176                                            std::move(colorSpaceXform),
177                                            std::move(maskProxy),
178                                            fInnerThreshold,
179                                            fOuterThreshold,
180                                            bounds));
181        if (!fp) {
182            return nullptr;
183        }
184
185        return DrawWithFP(context, std::move(fp), bounds, outProps);
186    }
187#endif
188
189    SkBitmap inputBM;
190
191    if (!input->getROPixels(&inputBM)) {
192        return nullptr;
193    }
194
195    if (inputBM.colorType() != kN32_SkColorType) {
196        return nullptr;
197    }
198
199    SkAutoLockPixels inputLock(inputBM);
200
201    if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) {
202        return nullptr;
203    }
204
205
206    SkMatrix localInverse;
207    if (!ctx.ctm().invert(&localInverse)) {
208        return nullptr;
209    }
210
211    SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(),
212                                            kPremul_SkAlphaType);
213
214    SkBitmap dst;
215    if (!dst.tryAllocPixels(info)) {
216        return nullptr;
217    }
218
219    SkAutoLockPixels dstLock(dst);
220
221    U8CPU innerThreshold = (U8CPU)(fInnerThreshold * 0xFF);
222    U8CPU outerThreshold = (U8CPU)(fOuterThreshold * 0xFF);
223    SkColor* dptr = dst.getAddr32(0, 0);
224    int dstWidth = dst.width(), dstHeight = dst.height();
225    SkIPoint srcOffset = { bounds.fLeft - inputOffset.fX, bounds.fTop - inputOffset.fY };
226    for (int y = 0; y < dstHeight; ++y) {
227        const SkColor* sptr = inputBM.getAddr32(srcOffset.fX, srcOffset.fY+y);
228
229        for (int x = 0; x < dstWidth; ++x) {
230            const SkColor& source = sptr[x];
231            SkColor outputColor(source);
232            SkPoint position;
233            localInverse.mapXY((SkScalar)x + bounds.fLeft, (SkScalar)y + bounds.fTop, &position);
234            if (fRegion.contains((int32_t)position.x(), (int32_t)position.y())) {
235                if (SkColorGetA(source) < innerThreshold) {
236                    U8CPU alpha = SkColorGetA(source);
237                    if (alpha == 0) {
238                        alpha = 1;
239                    }
240                    float scale = (float)innerThreshold / alpha;
241                    outputColor = SkColorSetARGB(innerThreshold,
242                                                  (U8CPU)(SkColorGetR(source) * scale),
243                                                  (U8CPU)(SkColorGetG(source) * scale),
244                                                  (U8CPU)(SkColorGetB(source) * scale));
245                }
246            } else {
247                if (SkColorGetA(source) > outerThreshold) {
248                    float scale = (float)outerThreshold / SkColorGetA(source);
249                    outputColor = SkColorSetARGB(outerThreshold,
250                                                  (U8CPU)(SkColorGetR(source) * scale),
251                                                  (U8CPU)(SkColorGetG(source) * scale),
252                                                  (U8CPU)(SkColorGetB(source) * scale));
253                }
254            }
255            dptr[y * dstWidth + x] = outputColor;
256        }
257    }
258
259    offset->fX = bounds.left();
260    offset->fY = bounds.top();
261    return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
262                                          dst);
263}
264
265#ifndef SK_IGNORE_TO_STRING
266void SkAlphaThresholdFilterImpl::toString(SkString* str) const {
267    str->appendf("SkAlphaThresholdImageFilter: (");
268    str->appendf("inner: %f outer: %f", fInnerThreshold, fOuterThreshold);
269    str->append(")");
270}
271#endif
272