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