• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 #ifndef SkLinearBitmapPipeline_tile_DEFINED
9 #define SkLinearBitmapPipeline_tile_DEFINED
10 
11 #include "SkLinearBitmapPipeline_core.h"
12 #include "SkPM4f.h"
13 #include <algorithm>
14 #include <cmath>
15 #include <limits>
16 
17 namespace {
18 
assertTiled(const Sk4s & vs,SkScalar vMax)19 void assertTiled(const Sk4s& vs, SkScalar vMax) {
20     SkASSERT(0 <= vs[0] && vs[0] < vMax);
21     SkASSERT(0 <= vs[1] && vs[1] < vMax);
22     SkASSERT(0 <= vs[2] && vs[2] < vMax);
23     SkASSERT(0 <= vs[3] && vs[3] < vMax);
24 }
25 
26 /*
27  * Clamp in the X direction.
28  * Observations:
29  *   * sample pointer border - if the sample point is <= 0.5 or >= Max - 0.5 then the pixel
30  *     value should be a border color. For this case, create the span using clampToSinglePixel.
31  */
32 class XClampStrategy {
33 public:
XClampStrategy(int32_t max)34     XClampStrategy(int32_t max)
35         : fXMaxPixel{SkScalar(max - SK_ScalarHalf)}
36         , fXMax{SkScalar(max)} { }
37 
tileXPoints(Sk4s * xs)38     void tileXPoints(Sk4s* xs) {
39         *xs = Sk4s::Min(Sk4s::Max(*xs, SK_ScalarHalf), fXMaxPixel);
40         assertTiled(*xs, fXMax);
41     }
42 
43     template<typename Next>
maybeProcessSpan(Span originalSpan,Next * next)44     bool maybeProcessSpan(Span originalSpan, Next* next) {
45         SkASSERT(!originalSpan.isEmpty());
46         SkPoint start; SkScalar length; int count;
47         std::tie(start, length, count) = originalSpan;
48         SkScalar x = X(start);
49         SkScalar y = Y(start);
50         Span span{{x, y}, length, count};
51 
52         if (span.completelyWithin(0.0f, fXMax)) {
53             next->pointSpan(span);
54             return true;
55         }
56         if (1 == count || 0.0f == length) {
57             return false;
58         }
59 
60         SkScalar dx = length / (count - 1);
61 
62         //    A                 B     C
63         // +-------+-------+-------++-------+-------+-------+     +-------+-------++------
64         // |  *---*|---*---|*---*--||-*---*-|---*---|*---...|     |--*---*|---*---||*---*....
65         // |       |       |       ||       |       |       | ... |       |       ||
66         // |       |       |       ||       |       |       |     |       |       ||
67         // +-------+-------+-------++-------+-------+-------+     +-------+-------++------
68         //                         ^                                              ^
69         //                         | xMin                                  xMax-1 | xMax
70         //
71         //     *---*---*---... - track of samples. * = sample
72         //
73         //     +-+                                 ||
74         //     | |  - pixels in source space.      || - tile border.
75         //     +-+                                 ||
76         //
77         // The length from A to B is the length in source space or 4 * dx or (count - 1) * dx
78         // where dx is the distance between samples. There are 5 destination pixels
79         // corresponding to 5 samples specified in the A, B span. The distance from A to the next
80         // span starting at C is 5 * dx, so count * dx.
81         // Remember, count is the number of pixels needed for the destination and the number of
82         // samples.
83         // Overall Strategy:
84         // * Under - for portions of the span < xMin, take the color at pixel {xMin, y} and use it
85         //   to fill in the 5 pixel sampled from A to B.
86         // * Middle - for the portion of the span between xMin and xMax sample normally.
87         // * Over - for the portion of the span > xMax, take the color at pixel {xMax-1, y} and
88         //   use it to fill in the rest of the destination pixels.
89         if (dx >= 0) {
90             Span leftClamped = span.breakAt(SK_ScalarHalf, dx);
91             if (!leftClamped.isEmpty()) {
92                 leftClamped.clampToSinglePixel({SK_ScalarHalf, y});
93                 next->pointSpan(leftClamped);
94             }
95             Span center = span.breakAt(fXMax, dx);
96             if (!center.isEmpty()) {
97                 next->pointSpan(center);
98             }
99             if (!span.isEmpty()) {
100                 span.clampToSinglePixel({fXMaxPixel, y});
101                 next->pointSpan(span);
102             }
103         } else {
104             Span rightClamped = span.breakAt(fXMax, dx);
105             if (!rightClamped.isEmpty()) {
106                 rightClamped.clampToSinglePixel({fXMaxPixel, y});
107                 next->pointSpan(rightClamped);
108             }
109             Span center = span.breakAt(SK_ScalarHalf, dx);
110             if (!center.isEmpty()) {
111                 next->pointSpan(center);
112             }
113             if (!span.isEmpty()) {
114                 span.clampToSinglePixel({SK_ScalarHalf, y});
115                 next->pointSpan(span);
116             }
117         }
118         return true;
119     }
120 
121 private:
122     const SkScalar fXMaxPixel;
123     const SkScalar fXMax;
124 };
125 
126 class YClampStrategy {
127 public:
YClampStrategy(int32_t max)128     YClampStrategy(int32_t max)
129         : fYMaxPixel{SkScalar(max) - SK_ScalarHalf} { }
130 
tileYPoints(Sk4s * ys)131     void tileYPoints(Sk4s* ys) {
132         *ys = Sk4s::Min(Sk4s::Max(*ys, SK_ScalarHalf), fYMaxPixel);
133         assertTiled(*ys, fYMaxPixel + SK_ScalarHalf);
134     }
135 
tileY(SkScalar y)136     SkScalar tileY(SkScalar y) {
137         Sk4f ys{y};
138         tileYPoints(&ys);
139         return ys[0];
140     }
141 
142 private:
143     const SkScalar fYMaxPixel;
144 };
145 
tile_mod(SkScalar x,SkScalar base,SkScalar cap)146 SkScalar tile_mod(SkScalar x, SkScalar base, SkScalar cap) {
147     // When x is a negative number *very* close to zero, the difference becomes 0 - (-base) = base
148     // which is an out of bound value. The min() corrects these problematic values.
149     return std::min(x - SkScalarFloorToScalar(x / base) * base, cap);
150 }
151 
152 class XRepeatStrategy {
153 public:
XRepeatStrategy(int32_t max)154     XRepeatStrategy(int32_t max)
155         : fXMax{SkScalar(max)}
156         , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))}
157         , fXInvMax{1.0f / SkScalar(max)} { }
158 
tileXPoints(Sk4s * xs)159     void tileXPoints(Sk4s* xs) {
160         Sk4s divX = *xs * fXInvMax;
161         Sk4s modX = *xs - divX.floor() * fXMax;
162         *xs = Sk4s::Min(fXCap, modX);
163         assertTiled(*xs, fXMax);
164     }
165 
166     template<typename Next>
maybeProcessSpan(Span originalSpan,Next * next)167     bool maybeProcessSpan(Span originalSpan, Next* next) {
168         SkASSERT(!originalSpan.isEmpty());
169         SkPoint start; SkScalar length; int count;
170         std::tie(start, length, count) = originalSpan;
171         // Make x and y in range on the tile.
172         SkScalar x = tile_mod(X(start), fXMax, fXCap);
173         SkScalar y = Y(start);
174         SkScalar dx = length / (count - 1);
175 
176         // No need trying to go fast because the steps are larger than a tile or there is one point.
177         if (SkScalarAbs(dx) >= fXMax || count <= 1) {
178             return false;
179         }
180 
181         //             A        B     C                  D                Z
182         // +-------+-------+-------++-------+-------+-------++     +-------+-------++------
183         // |       |   *---|*---*--||-*---*-|---*---|*---*--||     |--*---*|       ||
184         // |       |       |       ||       |       |       || ... |       |       ||
185         // |       |       |       ||       |       |       ||     |       |       ||
186         // +-------+-------+-------++-------+-------+-------++     +-------+-------++------
187         //                         ^^                       ^^                     ^^
188         //                    xMax || xMin             xMax || xMin           xMax || xMin
189         //
190         //     *---*---*---... - track of samples. * = sample
191         //
192         //     +-+                                 ||
193         //     | |  - pixels in source space.      || - tile border.
194         //     +-+                                 ||
195         //
196         //
197         // The given span starts at A and continues on through several tiles to sample point Z.
198         // The idea is to break this into several spans one on each tile the entire span
199         // intersects. The A to B span only covers a partial tile and has a count of 3 and the
200         // distance from A to B is (count - 1) * dx or 2 * dx. The distance from A to the start of
201         // the next span is count * dx or 3 * dx. Span C to D covers an entire tile has a count
202         // of 5 and a length of 4 * dx. Remember, count is the number of pixels needed for the
203         // destination and the number of samples.
204         //
205         // Overall Strategy:
206         // While the span hangs over the edge of the tile, draw the span covering the tile then
207         // slide the span over to the next tile.
208 
209         // The guard could have been count > 0, but then a bunch of math would be done in the
210         // common case.
211 
212         Span span({x, y}, length, count);
213         if (dx > 0) {
214             while (!span.isEmpty() && span.endX() >= fXMax) {
215                 Span toDraw = span.breakAt(fXMax, dx);
216                 next->pointSpan(toDraw);
217                 span.offset(-fXMax);
218             }
219         } else {
220             while (!span.isEmpty() && span.endX() < 0.0f) {
221                 Span toDraw = span.breakAt(0.0f, dx);
222                 next->pointSpan(toDraw);
223                 span.offset(fXMax);
224             }
225         }
226 
227         // All on a single tile.
228         if (!span.isEmpty()) {
229             next->pointSpan(span);
230         }
231 
232         return true;
233     }
234 
235 private:
236     const SkScalar fXMax;
237     const SkScalar fXCap;
238     const SkScalar fXInvMax;
239 };
240 
241 // The XRepeatUnitScaleStrategy exploits the situation where dx = 1.0. The main advantage is that
242 // the relationship between the sample points and the source pixels does not change from tile to
243 // repeated tile. This allows the tiler to calculate the span once and re-use it for each
244 // repeated tile. This is later exploited by some samplers to avoid converting pixels to linear
245 // space allowing the use of memmove to place pixel in the destination.
246 class XRepeatUnitScaleStrategy {
247 public:
XRepeatUnitScaleStrategy(int32_t max)248     XRepeatUnitScaleStrategy(int32_t max)
249         : fXMax{SkScalar(max)}
250         , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))}
251         , fXInvMax{1.0f / SkScalar(max)} { }
252 
tileXPoints(Sk4s * xs)253     void tileXPoints(Sk4s* xs) {
254         Sk4s divX = *xs * fXInvMax;
255         Sk4s modX = *xs - divX.floor() * fXMax;
256         *xs = Sk4s::Min(fXCap, modX);
257         assertTiled(*xs, fXMax);
258     }
259 
260     template<typename Next>
maybeProcessSpan(Span originalSpan,Next * next)261     bool maybeProcessSpan(Span originalSpan, Next* next) {
262         SkASSERT(!originalSpan.isEmpty());
263         SkPoint start; SkScalar length; int count;
264         std::tie(start, length, count) = originalSpan;
265         // Make x and y in range on the tile.
266         SkScalar x = tile_mod(X(start), fXMax, fXCap);
267         SkScalar y = Y(start);
268 
269         // No need trying to go fast because the steps are larger than a tile or there is one point.
270         if (fXMax == 1 || count <= 1) {
271             return false;
272         }
273 
274         // x should be on the tile.
275         SkASSERT(0.0f <= x && x < fXMax);
276         Span span({x, y}, length, count);
277 
278         if (SkScalarFloorToScalar(x) != 0.0f) {
279             Span toDraw = span.breakAt(fXMax, 1.0f);
280             SkASSERT(0.0f <= toDraw.startX() && toDraw.endX() < fXMax);
281             next->pointSpan(toDraw);
282             span.offset(-fXMax);
283         }
284 
285         // All of the span could have been on the first tile. If so, then no work to do.
286         if (span.isEmpty()) return true;
287 
288         // At this point the span should be aligned to zero.
289         SkASSERT(SkScalarFloorToScalar(span.startX()) == 0.0f);
290 
291         // Note: The span length has an unintuitive relation to the tile width. The tile width is
292         // a half open interval [tb, te), but the span is a closed interval [sb, se]. In order to
293         // compare the two, you need to convert the span to a half open interval. This is done by
294         // adding dx to se. So, the span becomes: [sb, se + dx). Hence the + 1.0f below.
295         SkScalar div = (span.length() + 1.0f) / fXMax;
296         int32_t repeatCount = SkScalarFloorToInt(div);
297         Span repeatableSpan{{0.0f, y}, fXMax - 1.0f, SkScalarFloorToInt(fXMax)};
298 
299         // Repeat the center section.
300         SkASSERT(0.0f <= repeatableSpan.startX() && repeatableSpan.endX() < fXMax);
301         if (repeatCount > 0) {
302             next->repeatSpan(repeatableSpan, repeatCount);
303         }
304 
305         // Calculate the advance past the center portion.
306         SkScalar advance = SkScalar(repeatCount) * fXMax;
307 
308         // There may be some of the span left over.
309         span.breakAt(advance, 1.0f);
310 
311         // All on a single tile.
312         if (!span.isEmpty()) {
313             span.offset(-advance);
314             SkASSERT(0.0f <= span.startX() && span.endX() < fXMax);
315             next->pointSpan(span);
316         }
317 
318         return true;
319     }
320 
321 private:
322     const SkScalar fXMax;
323     const SkScalar fXCap;
324     const SkScalar fXInvMax;
325 };
326 
327 class YRepeatStrategy {
328 public:
YRepeatStrategy(int32_t max)329     YRepeatStrategy(int32_t max)
330         : fYMax{SkScalar(max)}
331         , fYCap{SkScalar(nextafterf(SkScalar(max), 0.0f))}
332         , fYsInvMax{1.0f / SkScalar(max)} { }
333 
tileYPoints(Sk4s * ys)334     void tileYPoints(Sk4s* ys) {
335         Sk4s divY = *ys * fYsInvMax;
336         Sk4s modY = *ys - divY.floor() * fYMax;
337         *ys = Sk4s::Min(fYCap, modY);
338         assertTiled(*ys, fYMax);
339     }
340 
tileY(SkScalar y)341     SkScalar tileY(SkScalar y) {
342         SkScalar answer = tile_mod(y, fYMax, fYCap);
343         SkASSERT(0 <= answer && answer < fYMax);
344         return answer;
345     }
346 
347 private:
348     const SkScalar fYMax;
349     const SkScalar fYCap;
350     const SkScalar fYsInvMax;
351 };
352 // max = 40
353 // mq2[x_] := Abs[(x - 40) - Floor[(x - 40)/80] * 80 - 40]
354 class XMirrorStrategy {
355 public:
XMirrorStrategy(int32_t max)356     XMirrorStrategy(int32_t max)
357         : fXMax{SkScalar(max)}
358         , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))}
359         , fXDoubleInvMax{1.0f / (2.0f * SkScalar(max))} { }
360 
tileXPoints(Sk4s * xs)361     void tileXPoints(Sk4s* xs) {
362         Sk4f bias   = *xs - fXMax;
363         Sk4f div    = bias * fXDoubleInvMax;
364         Sk4f mod    = bias - div.floor() * 2.0f * fXMax;
365         Sk4f unbias = mod - fXMax;
366         *xs = Sk4f::Min(unbias.abs(), fXCap);
367         assertTiled(*xs, fXMax);
368     }
369 
370     template <typename Next>
maybeProcessSpan(Span originalSpan,Next * next)371     bool maybeProcessSpan(Span originalSpan, Next* next) { return false; }
372 
373 private:
374     SkScalar fXMax;
375     SkScalar fXCap;
376     SkScalar fXDoubleInvMax;
377 };
378 
379 class YMirrorStrategy {
380 public:
YMirrorStrategy(int32_t max)381     YMirrorStrategy(int32_t max)
382         : fYMax{SkScalar(max)}
383         , fYCap{nextafterf(SkScalar(max), 0.0f)}
384         , fYDoubleInvMax{1.0f / (2.0f * SkScalar(max))} { }
385 
tileYPoints(Sk4s * ys)386     void tileYPoints(Sk4s* ys) {
387         Sk4f bias   = *ys - fYMax;
388         Sk4f div    = bias * fYDoubleInvMax;
389         Sk4f mod    = bias - div.floor() * 2.0f * fYMax;
390         Sk4f unbias = mod - fYMax;
391         *ys = Sk4f::Min(unbias.abs(), fYCap);
392         assertTiled(*ys, fYMax);
393     }
394 
tileY(SkScalar y)395     SkScalar tileY(SkScalar y) {
396         SkScalar bias   = y - fYMax;
397         SkScalar div    = bias * fYDoubleInvMax;
398         SkScalar mod    = bias - SkScalarFloorToScalar(div) * 2.0f * fYMax;
399         SkScalar unbias = mod - fYMax;
400         SkScalar answer = SkMinScalar(SkScalarAbs(unbias), fYCap);
401         SkASSERT(0 <= answer && answer < fYMax);
402         return answer;
403     }
404 
405 private:
406     SkScalar fYMax;
407     SkScalar fYCap;
408     SkScalar fYDoubleInvMax;
409 };
410 
411 }  // namespace
412 #endif  // SkLinearBitmapPipeline_tile_DEFINED
413