1 /*
2 * Copyright 2012 The Android Open Source Project
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/core/SkAlphaType.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkColorType.h"
12 #include "include/core/SkFlattenable.h"
13 #include "include/core/SkImageFilter.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkM44.h"
16 #include "include/core/SkPoint.h"
17 #include "include/core/SkRect.h"
18 #include "include/core/SkRefCnt.h"
19 #include "include/core/SkScalar.h"
20 #include "include/core/SkTypes.h"
21 #include "include/effects/SkImageFilters.h"
22 #include "include/effects/SkRuntimeEffect.h"
23 #include "include/private/base/SkTPin.h"
24 #include "src/core/SkImageFilter_Base.h"
25 #include "src/core/SkReadBuffer.h"
26 #include "src/core/SkSpecialImage.h"
27 #include "src/core/SkValidationUtils.h"
28 #include "src/core/SkWriteBuffer.h"
29
30 #include <algorithm>
31 #include <memory>
32 #include <utility>
33
34 #if defined(SK_GANESH)
35 #include "src/core/SkRuntimeEffectPriv.h"
36 #include "src/gpu/ganesh/GrColorSpaceXform.h"
37 #include "src/gpu/ganesh/GrFragmentProcessor.h"
38 #include "src/gpu/ganesh/GrSurfaceProxy.h"
39 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
40 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
41 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
42 #endif
43
44 namespace {
45
46 class SkMagnifierImageFilter final : public SkImageFilter_Base {
47 public:
SkMagnifierImageFilter(const SkRect & srcRect,SkScalar inset,sk_sp<SkImageFilter> input,const SkRect * cropRect)48 SkMagnifierImageFilter(const SkRect& srcRect, SkScalar inset, sk_sp<SkImageFilter> input,
49 const SkRect* cropRect)
50 : INHERITED(&input, 1, cropRect)
51 , fSrcRect(srcRect)
52 , fInset(inset) {
53 SkASSERT(srcRect.left() >= 0 && srcRect.top() >= 0 && inset >= 0);
54 }
55
56 protected:
57 void flatten(SkWriteBuffer&) const override;
58
59 sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
60
61 private:
62 friend void ::SkRegisterMagnifierImageFilterFlattenable();
63 SK_FLATTENABLE_HOOKS(SkMagnifierImageFilter)
64
65 SkRect fSrcRect;
66 SkScalar fInset;
67
68 using INHERITED = SkImageFilter_Base;
69 };
70
71 } // end namespace
72
Magnifier(const SkRect & srcRect,SkScalar inset,sk_sp<SkImageFilter> input,const CropRect & cropRect)73 sk_sp<SkImageFilter> SkImageFilters::Magnifier(
74 const SkRect& srcRect, SkScalar inset, sk_sp<SkImageFilter> input,
75 const CropRect& cropRect) {
76 if (!SkScalarIsFinite(inset) || !SkIsValidRect(srcRect)) {
77 return nullptr;
78 }
79 if (inset < 0) {
80 return nullptr;
81 }
82 // Negative numbers in src rect are not supported
83 if (srcRect.fLeft < 0 || srcRect.fTop < 0) {
84 return nullptr;
85 }
86 return sk_sp<SkImageFilter>(new SkMagnifierImageFilter(srcRect, inset, std::move(input),
87 cropRect));
88 }
89
SkRegisterMagnifierImageFilterFlattenable()90 void SkRegisterMagnifierImageFilterFlattenable() {
91 SK_REGISTER_FLATTENABLE(SkMagnifierImageFilter);
92 // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
93 SkFlattenable::Register("SkMagnifierImageFilterImpl", SkMagnifierImageFilter::CreateProc);
94 }
95
CreateProc(SkReadBuffer & buffer)96 sk_sp<SkFlattenable> SkMagnifierImageFilter::CreateProc(SkReadBuffer& buffer) {
97 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
98 SkRect src;
99 buffer.readRect(&src);
100 return SkImageFilters::Magnifier(src, buffer.readScalar(), common.getInput(0),
101 common.cropRect());
102 }
103
flatten(SkWriteBuffer & buffer) const104 void SkMagnifierImageFilter::flatten(SkWriteBuffer& buffer) const {
105 this->INHERITED::flatten(buffer);
106 buffer.writeRect(fSrcRect);
107 buffer.writeScalar(fInset);
108 }
109
110 ////////////////////////////////////////////////////////////////////////////////
111
112 #if defined(SK_GANESH)
make_magnifier_fp(std::unique_ptr<GrFragmentProcessor> input,SkIRect bounds,SkRect srcRect,float xInvZoom,float yInvZoom,float xInvInset,float yInvInset)113 static std::unique_ptr<GrFragmentProcessor> make_magnifier_fp(
114 std::unique_ptr<GrFragmentProcessor> input,
115 SkIRect bounds,
116 SkRect srcRect,
117 float xInvZoom,
118 float yInvZoom,
119 float xInvInset,
120 float yInvInset) {
121 static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
122 "uniform shader src;"
123 "uniform float4 boundsUniform;"
124 "uniform float xInvZoom;"
125 "uniform float yInvZoom;"
126 "uniform float xInvInset;"
127 "uniform float yInvInset;"
128 "uniform half2 offset;"
129
130 "half4 main(float2 coord) {"
131 "float2 zoom_coord = offset + coord * float2(xInvZoom, yInvZoom);"
132 "float2 delta = (coord - boundsUniform.xy) * boundsUniform.zw;"
133 "delta = min(delta, float2(1.0) - delta);"
134 "delta *= float2(xInvInset, yInvInset);"
135
136 "float weight = 0.0;"
137 "if (delta.s < 2.0 && delta.t < 2.0) {"
138 "delta = float2(2.0) - delta;"
139 "float dist = length(delta);"
140 "dist = max(2.0 - dist, 0.0);"
141 "weight = min(dist * dist, 1.0);"
142 "} else {"
143 "float2 delta_squared = delta * delta;"
144 "weight = min(min(delta_squared.x, delta_squared.y), 1.0);"
145 "}"
146
147 "return src.eval(mix(coord, zoom_coord, weight));"
148 "}"
149 );
150
151 SkV4 boundsUniform = {static_cast<float>(bounds.x()),
152 static_cast<float>(bounds.y()),
153 1.f / bounds.width(),
154 1.f / bounds.height()};
155
156 return GrSkSLFP::Make(effect, "magnifier_fp", /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kNone,
157 "src", std::move(input),
158 "boundsUniform", boundsUniform,
159 "xInvZoom", xInvZoom,
160 "yInvZoom", yInvZoom,
161 "xInvInset", xInvInset,
162 "yInvInset", yInvInset,
163 "offset", SkV2{srcRect.x(), srcRect.y()});
164 }
165 #endif
166
onFilterImage(const Context & ctx,SkIPoint * offset) const167 sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(const Context& ctx,
168 SkIPoint* offset) const {
169 SkIPoint inputOffset = SkIPoint::Make(0, 0);
170 sk_sp<SkSpecialImage> input(this->filterInput(0, ctx, &inputOffset));
171 if (!input) {
172 return nullptr;
173 }
174
175 const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(),
176 input->width(), input->height());
177
178 SkIRect bounds;
179 if (!this->applyCropRect(ctx, inputBounds, &bounds)) {
180 return nullptr;
181 }
182
183 SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1;
184
185 SkScalar invXZoom = fSrcRect.width() / bounds.width();
186 SkScalar invYZoom = fSrcRect.height() / bounds.height();
187
188
189 #if defined(SK_GANESH)
190 if (ctx.gpuBacked()) {
191 auto context = ctx.getContext();
192
193 GrSurfaceProxyView inputView = input->view(context);
194 SkASSERT(inputView.asTextureProxy());
195
196 const auto isProtected = inputView.proxy()->isProtected();
197 const auto origin = inputView.origin();
198
199 offset->fX = bounds.left();
200 offset->fY = bounds.top();
201 bounds.offset(-inputOffset);
202
203 // Map bounds and srcRect into the proxy space. Due to the zoom effect,
204 // it's not just an offset for fSrcRect.
205 bounds.offset(input->subset().x(), input->subset().y());
206 SkRect srcRect = fSrcRect.makeOffset((1.f - invXZoom) * input->subset().x(),
207 (1.f - invYZoom) * input->subset().y());
208 auto inputFP = GrTextureEffect::Make(std::move(inputView), kPremul_SkAlphaType);
209
210 auto fp = make_magnifier_fp(std::move(inputFP),
211 bounds,
212 srcRect,
213 invXZoom,
214 invYZoom,
215 bounds.width() * invInset,
216 bounds.height() * invInset);
217
218 fp = GrColorSpaceXformEffect::Make(std::move(fp),
219 input->getColorSpace(), input->alphaType(),
220 ctx.colorSpace(), kPremul_SkAlphaType);
221 if (!fp) {
222 return nullptr;
223 }
224
225 return DrawWithFP(context, std::move(fp), bounds, ctx.colorType(), ctx.colorSpace(),
226 ctx.surfaceProps(), origin, isProtected);
227 }
228 #endif
229
230 SkBitmap inputBM;
231
232 if (!input->getROPixels(&inputBM)) {
233 return nullptr;
234 }
235
236 if ((inputBM.colorType() != kN32_SkColorType) ||
237 (fSrcRect.width() >= inputBM.width()) || (fSrcRect.height() >= inputBM.height())) {
238 return nullptr;
239 }
240
241 SkASSERT(inputBM.getPixels());
242 if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) {
243 return nullptr;
244 }
245
246 const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height());
247
248 SkBitmap dst;
249 if (!dst.tryAllocPixels(info)) {
250 return nullptr;
251 }
252
253 SkColor* dptr = dst.getAddr32(0, 0);
254 int dstWidth = dst.width(), dstHeight = dst.height();
255 for (int y = 0; y < dstHeight; ++y) {
256 for (int x = 0; x < dstWidth; ++x) {
257 SkScalar x_dist = std::min(x, dstWidth - x - 1) * invInset;
258 SkScalar y_dist = std::min(y, dstHeight - y - 1) * invInset;
259 SkScalar weight = 0;
260
261 static const SkScalar kScalar2 = SkScalar(2);
262
263 // To create a smooth curve at the corners, we need to work on
264 // a square twice the size of the inset.
265 if (x_dist < kScalar2 && y_dist < kScalar2) {
266 x_dist = kScalar2 - x_dist;
267 y_dist = kScalar2 - y_dist;
268
269 SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) +
270 SkScalarSquare(y_dist));
271 dist = std::max(kScalar2 - dist, 0.0f);
272 // SkTPin rather than std::max to handle potential NaN
273 weight = SkTPin(SkScalarSquare(dist), 0.0f, SK_Scalar1);
274 } else {
275 SkScalar sqDist = std::min(SkScalarSquare(x_dist),
276 SkScalarSquare(y_dist));
277 // SkTPin rather than std::max to handle potential NaN
278 weight = SkTPin(sqDist, 0.0f, SK_Scalar1);
279 }
280
281 SkScalar x_interp = weight * (fSrcRect.x() + x * invXZoom) + (1 - weight) * x;
282 SkScalar y_interp = weight * (fSrcRect.y() + y * invYZoom) + (1 - weight) * y;
283
284 int x_val = SkTPin(bounds.x() + SkScalarFloorToInt(x_interp), 0, inputBM.width() - 1);
285 int y_val = SkTPin(bounds.y() + SkScalarFloorToInt(y_interp), 0, inputBM.height() - 1);
286
287 *dptr = *inputBM.getAddr32(x_val, y_val);
288 dptr++;
289 }
290 }
291
292 offset->fX = bounds.left();
293 offset->fY = bounds.top();
294 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()),
295 dst, ctx.surfaceProps());
296 }
297