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