xref: /aosp_15_r20/external/skia/gm/imagemagnifier.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2012 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "gm/gm.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImage.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageFilter.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPixelRef.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRRect.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypeface.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkImageFilters.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkRandom.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "tools/ToolUtils.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "tools/fonts/FontToolUtils.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "tools/timer/TimeUtils.h"
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
30*c8dee2aaSAndroid Build Coastguard Worker 
31*c8dee2aaSAndroid Build Coastguard Worker #define WIDTH 500
32*c8dee2aaSAndroid Build Coastguard Worker #define HEIGHT 500
33*c8dee2aaSAndroid Build Coastguard Worker 
draw_content(SkCanvas * canvas,float maxTextSize,int count)34*c8dee2aaSAndroid Build Coastguard Worker static void draw_content(SkCanvas* canvas, float maxTextSize, int count) {
35*c8dee2aaSAndroid Build Coastguard Worker     const char* str = "The quick brown fox jumped over the lazy dog.";
36*c8dee2aaSAndroid Build Coastguard Worker     SkRandom rand;
37*c8dee2aaSAndroid Build Coastguard Worker     SkFont   font = ToolUtils::DefaultPortableFont();
38*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < count; ++i) {
39*c8dee2aaSAndroid Build Coastguard Worker         int x = rand.nextULessThan(WIDTH);
40*c8dee2aaSAndroid Build Coastguard Worker         int y = rand.nextULessThan(HEIGHT);
41*c8dee2aaSAndroid Build Coastguard Worker         SkPaint paint;
42*c8dee2aaSAndroid Build Coastguard Worker         paint.setColor(ToolUtils::color_to_565(rand.nextBits(24) | 0xFF000000));
43*c8dee2aaSAndroid Build Coastguard Worker         font.setSize(rand.nextRangeScalar(0, maxTextSize));
44*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawString(str, SkIntToScalar(x), SkIntToScalar(y), font, paint);
45*c8dee2aaSAndroid Build Coastguard Worker     }
46*c8dee2aaSAndroid Build Coastguard Worker }
47*c8dee2aaSAndroid Build Coastguard Worker 
DEF_SIMPLE_GM_BG(imagemagnifier,canvas,WIDTH,HEIGHT,SK_ColorBLACK)48*c8dee2aaSAndroid Build Coastguard Worker DEF_SIMPLE_GM_BG(imagemagnifier, canvas, WIDTH, HEIGHT, SK_ColorBLACK) {
49*c8dee2aaSAndroid Build Coastguard Worker         SkPaint filterPaint;
50*c8dee2aaSAndroid Build Coastguard Worker         filterPaint.setImageFilter(
51*c8dee2aaSAndroid Build Coastguard Worker                 SkImageFilters::Magnifier(SkRect::MakeWH(WIDTH, HEIGHT), 2.f, 100.f,
52*c8dee2aaSAndroid Build Coastguard Worker                                           SkFilterMode::kLinear, nullptr));
53*c8dee2aaSAndroid Build Coastguard Worker         canvas->saveLayer(nullptr, &filterPaint);
54*c8dee2aaSAndroid Build Coastguard Worker         draw_content(canvas, 300.f, 25);
55*c8dee2aaSAndroid Build Coastguard Worker         canvas->restore();
56*c8dee2aaSAndroid Build Coastguard Worker }
57*c8dee2aaSAndroid Build Coastguard Worker 
58*c8dee2aaSAndroid Build Coastguard Worker ////////////////////////////////////////////////////////////////////////////////
59*c8dee2aaSAndroid Build Coastguard Worker #define WIDTH_HEIGHT 256
60*c8dee2aaSAndroid Build Coastguard Worker 
make_img()61*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkImage> make_img() {
62*c8dee2aaSAndroid Build Coastguard Worker     SkBitmap bitmap;
63*c8dee2aaSAndroid Build Coastguard Worker     bitmap.allocN32Pixels(WIDTH_HEIGHT, WIDTH_HEIGHT);
64*c8dee2aaSAndroid Build Coastguard Worker     SkCanvas canvas(bitmap);
65*c8dee2aaSAndroid Build Coastguard Worker 
66*c8dee2aaSAndroid Build Coastguard Worker     canvas.clear(0x0);
67*c8dee2aaSAndroid Build Coastguard Worker 
68*c8dee2aaSAndroid Build Coastguard Worker     SkPaint paint;
69*c8dee2aaSAndroid Build Coastguard Worker     paint.setColor(SK_ColorBLUE);
70*c8dee2aaSAndroid Build Coastguard Worker 
71*c8dee2aaSAndroid Build Coastguard Worker     for (float pos = 0; pos < WIDTH_HEIGHT; pos += 16) {
72*c8dee2aaSAndroid Build Coastguard Worker         canvas.drawLine(0, pos, SkIntToScalar(WIDTH_HEIGHT), pos, paint);
73*c8dee2aaSAndroid Build Coastguard Worker         canvas.drawLine(pos, 0, pos, SkIntToScalar(WIDTH_HEIGHT), paint);
74*c8dee2aaSAndroid Build Coastguard Worker     }
75*c8dee2aaSAndroid Build Coastguard Worker 
76*c8dee2aaSAndroid Build Coastguard Worker     SkBitmap result;
77*c8dee2aaSAndroid Build Coastguard Worker     result.setInfo(SkImageInfo::MakeS32(WIDTH_HEIGHT, WIDTH_HEIGHT, kPremul_SkAlphaType));
78*c8dee2aaSAndroid Build Coastguard Worker     result.setPixelRef(sk_ref_sp(bitmap.pixelRef()), 0, 0);
79*c8dee2aaSAndroid Build Coastguard Worker 
80*c8dee2aaSAndroid Build Coastguard Worker     return result.asImage();
81*c8dee2aaSAndroid Build Coastguard Worker }
82*c8dee2aaSAndroid Build Coastguard Worker 
DEF_SIMPLE_GM_BG(imagemagnifier_cropped,canvas,WIDTH_HEIGHT,WIDTH_HEIGHT,SK_ColorBLACK)83*c8dee2aaSAndroid Build Coastguard Worker DEF_SIMPLE_GM_BG(imagemagnifier_cropped, canvas, WIDTH_HEIGHT, WIDTH_HEIGHT, SK_ColorBLACK) {
84*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkImageFilter> imageSource(SkImageFilters::Image(make_img(), SkFilterMode::kNearest));
85*c8dee2aaSAndroid Build Coastguard Worker 
86*c8dee2aaSAndroid Build Coastguard Worker     // Crop out a 16 pixel ring around the result
87*c8dee2aaSAndroid Build Coastguard Worker     const SkIRect cropRect = SkIRect::MakeXYWH(16, 16, WIDTH_HEIGHT-32, WIDTH_HEIGHT-32);
88*c8dee2aaSAndroid Build Coastguard Worker 
89*c8dee2aaSAndroid Build Coastguard Worker     SkPaint filterPaint;
90*c8dee2aaSAndroid Build Coastguard Worker     filterPaint.setImageFilter(SkImageFilters::Magnifier(
91*c8dee2aaSAndroid Build Coastguard Worker             SkRect::MakeWH(WIDTH_HEIGHT, WIDTH_HEIGHT),
92*c8dee2aaSAndroid Build Coastguard Worker             WIDTH_HEIGHT / (WIDTH_HEIGHT - 96.f), 64.f, {},
93*c8dee2aaSAndroid Build Coastguard Worker             std::move(imageSource),  &cropRect));
94*c8dee2aaSAndroid Build Coastguard Worker 
95*c8dee2aaSAndroid Build Coastguard Worker     canvas->saveLayer(nullptr, &filterPaint);
96*c8dee2aaSAndroid Build Coastguard Worker     canvas->restore();
97*c8dee2aaSAndroid Build Coastguard Worker }
98*c8dee2aaSAndroid Build Coastguard Worker 
99*c8dee2aaSAndroid Build Coastguard Worker class ImageMagnifierBounds : public skiagm::GM {
100*c8dee2aaSAndroid Build Coastguard Worker public:
ImageMagnifierBounds()101*c8dee2aaSAndroid Build Coastguard Worker     ImageMagnifierBounds() : fX(0.f), fY(0.f) {}
102*c8dee2aaSAndroid Build Coastguard Worker 
103*c8dee2aaSAndroid Build Coastguard Worker protected:
getName() const104*c8dee2aaSAndroid Build Coastguard Worker     SkString getName() const override { return SkString("imagemagnifier_bounds"); }
getISize()105*c8dee2aaSAndroid Build Coastguard Worker     SkISize getISize() override { return SkISize::Make(768, 512); }
106*c8dee2aaSAndroid Build Coastguard Worker 
onAnimate(double nanos)107*c8dee2aaSAndroid Build Coastguard Worker     bool onAnimate(double nanos) override {
108*c8dee2aaSAndroid Build Coastguard Worker         fX = TimeUtils::SineWave(nanos, 10.f, 0.f, -200.f, 200.f);
109*c8dee2aaSAndroid Build Coastguard Worker         fY = TimeUtils::SineWave(nanos, 10.f, 3.f, -200.f, 200.f);
110*c8dee2aaSAndroid Build Coastguard Worker         return true;
111*c8dee2aaSAndroid Build Coastguard Worker     }
112*c8dee2aaSAndroid Build Coastguard Worker 
onDraw(SkCanvas * canvas)113*c8dee2aaSAndroid Build Coastguard Worker     void onDraw(SkCanvas* canvas) override {
114*c8dee2aaSAndroid Build Coastguard Worker         this->drawRow(canvas, 16.f); // fish eye distortion
115*c8dee2aaSAndroid Build Coastguard Worker         canvas->translate(0.f, 256.f);
116*c8dee2aaSAndroid Build Coastguard Worker         this->drawRow(canvas, 0.f);  // no distortion, just zoom
117*c8dee2aaSAndroid Build Coastguard Worker     }
118*c8dee2aaSAndroid Build Coastguard Worker 
119*c8dee2aaSAndroid Build Coastguard Worker private:
120*c8dee2aaSAndroid Build Coastguard Worker 
drawRow(SkCanvas * canvas,float inset)121*c8dee2aaSAndroid Build Coastguard Worker     void drawRow(SkCanvas* canvas, float inset) {
122*c8dee2aaSAndroid Build Coastguard Worker         // Draw the magnifier two ways: backdrop filtered and then through a saveLayer with a
123*c8dee2aaSAndroid Build Coastguard Worker         // regular filter. Lastly draw the un-filtered input. Relevant bounds are displayed on
124*c8dee2aaSAndroid Build Coastguard Worker         // top of the rendering:
125*c8dee2aaSAndroid Build Coastguard Worker         //  - black = the lens bounding box
126*c8dee2aaSAndroid Build Coastguard Worker         //  - red   = the clipped inset lens bounds
127*c8dee2aaSAndroid Build Coastguard Worker         //  - blue  = the source of the undistorted magnified content
128*c8dee2aaSAndroid Build Coastguard Worker         auto drawBorder = [canvas](SkRect rect, SkColor color,
129*c8dee2aaSAndroid Build Coastguard Worker                                    float width, float borderInset = 0.f) {
130*c8dee2aaSAndroid Build Coastguard Worker             SkPaint paint;
131*c8dee2aaSAndroid Build Coastguard Worker             paint.setStyle(SkPaint::kStroke_Style);
132*c8dee2aaSAndroid Build Coastguard Worker             paint.setStrokeWidth(width);
133*c8dee2aaSAndroid Build Coastguard Worker             paint.setColor(color);
134*c8dee2aaSAndroid Build Coastguard Worker             paint.setAntiAlias(true);
135*c8dee2aaSAndroid Build Coastguard Worker 
136*c8dee2aaSAndroid Build Coastguard Worker             // This draws the original rect (unrounded) when borderInset = 0
137*c8dee2aaSAndroid Build Coastguard Worker             rect.inset(borderInset, borderInset);
138*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawRRect(SkRRect::MakeRectXY(rect, borderInset, borderInset), paint);
139*c8dee2aaSAndroid Build Coastguard Worker         };
140*c8dee2aaSAndroid Build Coastguard Worker 
141*c8dee2aaSAndroid Build Coastguard Worker         // Logically there is a 'widgetBounds' that is the region of pixels to
142*c8dee2aaSAndroid Build Coastguard Worker         // be filled with magnified content. Pixels inside widgetBounds are
143*c8dee2aaSAndroid Build Coastguard Worker         // scaled up by a factor of 'zoomAmount', with a non linear distortion
144*c8dee2aaSAndroid Build Coastguard Worker         // applied to pixels up to 'inset' inside 'widgetBounds'. The specific
145*c8dee2aaSAndroid Build Coastguard Worker         // linearly scaled region is termed the 'srcRect' and is adjusted
146*c8dee2aaSAndroid Build Coastguard Worker         // dynamically if parts of 'widgetBounds' are offscreen.
147*c8dee2aaSAndroid Build Coastguard Worker         SkRect widgetBounds = {16.f, 24.f, 220.f, 248.f};
148*c8dee2aaSAndroid Build Coastguard Worker         widgetBounds.offset(fX, fY); // animating helps highlight magnifier behavior
149*c8dee2aaSAndroid Build Coastguard Worker 
150*c8dee2aaSAndroid Build Coastguard Worker         constexpr float kZoomAmount = 2.5f;
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker         // The available content for backdrops, which clips the widgetBounds as it animates.
153*c8dee2aaSAndroid Build Coastguard Worker         constexpr SkRect kOutBounds = {0.f, 0.f, 256.f, 256.f};
154*c8dee2aaSAndroid Build Coastguard Worker 
155*c8dee2aaSAndroid Build Coastguard Worker         // The filter responds to any crop (explicit or from missing backdrop content). Compute
156*c8dee2aaSAndroid Build Coastguard Worker         // the corresponding clipped bounds and source bounds for visualization purposes.
157*c8dee2aaSAndroid Build Coastguard Worker         SkPoint zoomCenter = widgetBounds.center();
158*c8dee2aaSAndroid Build Coastguard Worker         SkRect clippedWidget = widgetBounds;
159*c8dee2aaSAndroid Build Coastguard Worker         SkAssertResult(clippedWidget.intersect(kOutBounds));
160*c8dee2aaSAndroid Build Coastguard Worker         zoomCenter = {SkTPin(zoomCenter.fX, clippedWidget.fLeft, clippedWidget.fRight),
161*c8dee2aaSAndroid Build Coastguard Worker                       SkTPin(zoomCenter.fY, clippedWidget.fTop, clippedWidget.fBottom)};
162*c8dee2aaSAndroid Build Coastguard Worker         zoomCenter = zoomCenter * (1.f - 1.f / kZoomAmount);
163*c8dee2aaSAndroid Build Coastguard Worker         SkRect srcRect = {clippedWidget.fLeft   / kZoomAmount + zoomCenter.fX,
164*c8dee2aaSAndroid Build Coastguard Worker                           clippedWidget.fTop    / kZoomAmount + zoomCenter.fY,
165*c8dee2aaSAndroid Build Coastguard Worker                           clippedWidget.fRight  / kZoomAmount + zoomCenter.fX,
166*c8dee2aaSAndroid Build Coastguard Worker                           clippedWidget.fBottom / kZoomAmount + zoomCenter.fY};
167*c8dee2aaSAndroid Build Coastguard Worker 
168*c8dee2aaSAndroid Build Coastguard Worker         // Internally, the magnifier filter performs equivalent calculations but responds to the
169*c8dee2aaSAndroid Build Coastguard Worker         // canvas matrix and available input automatically.
170*c8dee2aaSAndroid Build Coastguard Worker         sk_sp<SkImageFilter> magnifier =
171*c8dee2aaSAndroid Build Coastguard Worker                 SkImageFilters::Magnifier(widgetBounds, kZoomAmount, inset,
172*c8dee2aaSAndroid Build Coastguard Worker                                           SkFilterMode::kLinear, nullptr, kOutBounds);
173*c8dee2aaSAndroid Build Coastguard Worker 
174*c8dee2aaSAndroid Build Coastguard Worker         // Draw once as a backdrop filter
175*c8dee2aaSAndroid Build Coastguard Worker         canvas->save();
176*c8dee2aaSAndroid Build Coastguard Worker             canvas->clipRect(kOutBounds);
177*c8dee2aaSAndroid Build Coastguard Worker             draw_content(canvas, 32.f, 350);
178*c8dee2aaSAndroid Build Coastguard Worker             canvas->saveLayer({nullptr, nullptr, magnifier.get(), 0});
179*c8dee2aaSAndroid Build Coastguard Worker             canvas->restore();
180*c8dee2aaSAndroid Build Coastguard Worker 
181*c8dee2aaSAndroid Build Coastguard Worker             drawBorder(widgetBounds, SK_ColorBLACK, 2.f);
182*c8dee2aaSAndroid Build Coastguard Worker             if (inset > 0.f) {
183*c8dee2aaSAndroid Build Coastguard Worker                 drawBorder(clippedWidget, SK_ColorRED, 2.f, inset);
184*c8dee2aaSAndroid Build Coastguard Worker             }
185*c8dee2aaSAndroid Build Coastguard Worker         canvas->restore();
186*c8dee2aaSAndroid Build Coastguard Worker 
187*c8dee2aaSAndroid Build Coastguard Worker         // Draw once as a regular filter
188*c8dee2aaSAndroid Build Coastguard Worker         canvas->save();
189*c8dee2aaSAndroid Build Coastguard Worker             canvas->translate(256.f, 0.f);
190*c8dee2aaSAndroid Build Coastguard Worker             canvas->clipRect(kOutBounds);
191*c8dee2aaSAndroid Build Coastguard Worker 
192*c8dee2aaSAndroid Build Coastguard Worker             SkPaint paint;
193*c8dee2aaSAndroid Build Coastguard Worker             paint.setImageFilter(magnifier);
194*c8dee2aaSAndroid Build Coastguard Worker             canvas->saveLayer(nullptr, &paint);
195*c8dee2aaSAndroid Build Coastguard Worker                 draw_content(canvas, 32.f, 350);
196*c8dee2aaSAndroid Build Coastguard Worker             canvas->restore();
197*c8dee2aaSAndroid Build Coastguard Worker 
198*c8dee2aaSAndroid Build Coastguard Worker             drawBorder(widgetBounds, SK_ColorBLACK, 2.f);
199*c8dee2aaSAndroid Build Coastguard Worker             if (inset > 0.f) {
200*c8dee2aaSAndroid Build Coastguard Worker                 drawBorder(clippedWidget, SK_ColorRED, 2.f, inset);
201*c8dee2aaSAndroid Build Coastguard Worker             }
202*c8dee2aaSAndroid Build Coastguard Worker         canvas->restore();
203*c8dee2aaSAndroid Build Coastguard Worker 
204*c8dee2aaSAndroid Build Coastguard Worker         // Draw once unfiltered
205*c8dee2aaSAndroid Build Coastguard Worker         canvas->save();
206*c8dee2aaSAndroid Build Coastguard Worker             canvas->translate(512.f, 0.f);
207*c8dee2aaSAndroid Build Coastguard Worker             canvas->clipRect(kOutBounds);
208*c8dee2aaSAndroid Build Coastguard Worker             draw_content(canvas, 32.f, 350);
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker             drawBorder(widgetBounds, SK_ColorBLACK, 2.f);
211*c8dee2aaSAndroid Build Coastguard Worker             drawBorder(srcRect, SK_ColorBLUE, 2.f, inset / kZoomAmount);
212*c8dee2aaSAndroid Build Coastguard Worker         canvas->restore();
213*c8dee2aaSAndroid Build Coastguard Worker     }
214*c8dee2aaSAndroid Build Coastguard Worker 
215*c8dee2aaSAndroid Build Coastguard Worker private:
216*c8dee2aaSAndroid Build Coastguard Worker     SkScalar fX;
217*c8dee2aaSAndroid Build Coastguard Worker     SkScalar fY;
218*c8dee2aaSAndroid Build Coastguard Worker };
219*c8dee2aaSAndroid Build Coastguard Worker 
220*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new ImageMagnifierBounds(); )
221