diff options
Diffstat (limited to 'src/core/SkLinearBitmapPipeline_tile.h')
-rw-r--r-- | src/core/SkLinearBitmapPipeline_tile.h | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/src/core/SkLinearBitmapPipeline_tile.h b/src/core/SkLinearBitmapPipeline_tile.h new file mode 100644 index 0000000000..e18f7a1a5d --- /dev/null +++ b/src/core/SkLinearBitmapPipeline_tile.h @@ -0,0 +1,412 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLinearBitmapPipeline_tile_DEFINED +#define SkLinearBitmapPipeline_tile_DEFINED + +#include "SkLinearBitmapPipeline_core.h" +#include "SkPM4f.h" +#include <algorithm> +#include <cmath> +#include <limits> + +namespace { + +void assertTiled(const Sk4s& vs, SkScalar vMax) { + SkASSERT(0 <= vs[0] && vs[0] < vMax); + SkASSERT(0 <= vs[1] && vs[1] < vMax); + SkASSERT(0 <= vs[2] && vs[2] < vMax); + SkASSERT(0 <= vs[3] && vs[3] < vMax); +} + +/* + * Clamp in the X direction. + * Observations: + * * sample pointer border - if the sample point is <= 0.5 or >= Max - 0.5 then the pixel + * value should be a border color. For this case, create the span using clampToSinglePixel. + */ +class XClampStrategy { +public: + XClampStrategy(int32_t max) + : fXMaxPixel{SkScalar(max - SK_ScalarHalf)} + , fXMax{SkScalar(max)} { } + + void tileXPoints(Sk4s* xs) { + *xs = Sk4s::Min(Sk4s::Max(*xs, SK_ScalarHalf), fXMaxPixel); + assertTiled(*xs, fXMax); + } + + template<typename Next> + bool maybeProcessSpan(Span originalSpan, Next* next) { + SkASSERT(!originalSpan.isEmpty()); + SkPoint start; SkScalar length; int count; + std::tie(start, length, count) = originalSpan; + SkScalar x = X(start); + SkScalar y = Y(start); + Span span{{x, y}, length, count}; + + if (span.completelyWithin(0.0f, fXMax)) { + next->pointSpan(span); + return true; + } + if (1 == count || 0.0f == length) { + return false; + } + + SkScalar dx = length / (count - 1); + + // A B C + // +-------+-------+-------++-------+-------+-------+ +-------+-------++------ + // | *---*|---*---|*---*--||-*---*-|---*---|*---...| |--*---*|---*---||*---*.... + // | | | || | | | ... | | || + // | | | || | | | | | || + // +-------+-------+-------++-------+-------+-------+ +-------+-------++------ + // ^ ^ + // | xMin xMax-1 | xMax + // + // *---*---*---... - track of samples. * = sample + // + // +-+ || + // | | - pixels in source space. || - tile border. + // +-+ || + // + // The length from A to B is the length in source space or 4 * dx or (count - 1) * dx + // where dx is the distance between samples. There are 5 destination pixels + // corresponding to 5 samples specified in the A, B span. The distance from A to the next + // span starting at C is 5 * dx, so count * dx. + // Remember, count is the number of pixels needed for the destination and the number of + // samples. + // Overall Strategy: + // * Under - for portions of the span < xMin, take the color at pixel {xMin, y} and use it + // to fill in the 5 pixel sampled from A to B. + // * Middle - for the portion of the span between xMin and xMax sample normally. + // * Over - for the portion of the span > xMax, take the color at pixel {xMax-1, y} and + // use it to fill in the rest of the destination pixels. + if (dx >= 0) { + Span leftClamped = span.breakAt(SK_ScalarHalf, dx); + if (!leftClamped.isEmpty()) { + leftClamped.clampToSinglePixel({SK_ScalarHalf, y}); + next->pointSpan(leftClamped); + } + Span center = span.breakAt(fXMax, dx); + if (!center.isEmpty()) { + next->pointSpan(center); + } + if (!span.isEmpty()) { + span.clampToSinglePixel({fXMaxPixel, y}); + next->pointSpan(span); + } + } else { + Span rightClamped = span.breakAt(fXMax, dx); + if (!rightClamped.isEmpty()) { + rightClamped.clampToSinglePixel({fXMaxPixel, y}); + next->pointSpan(rightClamped); + } + Span center = span.breakAt(SK_ScalarHalf, dx); + if (!center.isEmpty()) { + next->pointSpan(center); + } + if (!span.isEmpty()) { + span.clampToSinglePixel({SK_ScalarHalf, y}); + next->pointSpan(span); + } + } + return true; + } + +private: + const SkScalar fXMaxPixel; + const SkScalar fXMax; +}; + +class YClampStrategy { +public: + YClampStrategy(int32_t max) + : fYMaxPixel{SkScalar(max) - SK_ScalarHalf} { } + + void tileYPoints(Sk4s* ys) { + *ys = Sk4s::Min(Sk4s::Max(*ys, SK_ScalarHalf), fYMaxPixel); + assertTiled(*ys, fYMaxPixel + SK_ScalarHalf); + } + + SkScalar tileY(SkScalar y) { + Sk4f ys{y}; + tileYPoints(&ys); + return ys[0]; + } + +private: + const SkScalar fYMaxPixel; +}; + +SkScalar tile_mod(SkScalar x, SkScalar base, SkScalar cap) { + // When x is a negative number *very* close to zero, the difference becomes 0 - (-base) = base + // which is an out of bound value. The min() corrects these problematic values. + return std::min(x - SkScalarFloorToScalar(x / base) * base, cap); +} + +class XRepeatStrategy { +public: + XRepeatStrategy(int32_t max) + : fXMax{SkScalar(max)} + , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))} + , fXInvMax{1.0f / SkScalar(max)} { } + + void tileXPoints(Sk4s* xs) { + Sk4s divX = *xs * fXInvMax; + Sk4s modX = *xs - divX.floor() * fXMax; + *xs = Sk4s::Min(fXCap, modX); + assertTiled(*xs, fXMax); + } + + template<typename Next> + bool maybeProcessSpan(Span originalSpan, Next* next) { + SkASSERT(!originalSpan.isEmpty()); + SkPoint start; SkScalar length; int count; + std::tie(start, length, count) = originalSpan; + // Make x and y in range on the tile. + SkScalar x = tile_mod(X(start), fXMax, fXCap); + SkScalar y = Y(start); + SkScalar dx = length / (count - 1); + + // No need trying to go fast because the steps are larger than a tile or there is one point. + if (SkScalarAbs(dx) >= fXMax || count <= 1) { + return false; + } + + // A B C D Z + // +-------+-------+-------++-------+-------+-------++ +-------+-------++------ + // | | *---|*---*--||-*---*-|---*---|*---*--|| |--*---*| || + // | | | || | | || ... | | || + // | | | || | | || | | || + // +-------+-------+-------++-------+-------+-------++ +-------+-------++------ + // ^^ ^^ ^^ + // xMax || xMin xMax || xMin xMax || xMin + // + // *---*---*---... - track of samples. * = sample + // + // +-+ || + // | | - pixels in source space. || - tile border. + // +-+ || + // + // + // The given span starts at A and continues on through several tiles to sample point Z. + // The idea is to break this into several spans one on each tile the entire span + // intersects. The A to B span only covers a partial tile and has a count of 3 and the + // distance from A to B is (count - 1) * dx or 2 * dx. The distance from A to the start of + // the next span is count * dx or 3 * dx. Span C to D covers an entire tile has a count + // of 5 and a length of 4 * dx. Remember, count is the number of pixels needed for the + // destination and the number of samples. + // + // Overall Strategy: + // While the span hangs over the edge of the tile, draw the span covering the tile then + // slide the span over to the next tile. + + // The guard could have been count > 0, but then a bunch of math would be done in the + // common case. + + Span span({x, y}, length, count); + if (dx > 0) { + while (!span.isEmpty() && span.endX() >= fXMax) { + Span toDraw = span.breakAt(fXMax, dx); + next->pointSpan(toDraw); + span.offset(-fXMax); + } + } else { + while (!span.isEmpty() && span.endX() < 0.0f) { + Span toDraw = span.breakAt(0.0f, dx); + next->pointSpan(toDraw); + span.offset(fXMax); + } + } + + // All on a single tile. + if (!span.isEmpty()) { + next->pointSpan(span); + } + + return true; + } + +private: + const SkScalar fXMax; + const SkScalar fXCap; + const SkScalar fXInvMax; +}; + +// The XRepeatUnitScaleStrategy exploits the situation where dx = 1.0. The main advantage is that +// the relationship between the sample points and the source pixels does not change from tile to +// repeated tile. This allows the tiler to calculate the span once and re-use it for each +// repeated tile. This is later exploited by some samplers to avoid converting pixels to linear +// space allowing the use of memmove to place pixel in the destination. +class XRepeatUnitScaleStrategy { +public: + XRepeatUnitScaleStrategy(int32_t max) + : fXMax{SkScalar(max)} + , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))} + , fXInvMax{1.0f / SkScalar(max)} { } + + void tileXPoints(Sk4s* xs) { + Sk4s divX = *xs * fXInvMax; + Sk4s modX = *xs - divX.floor() * fXMax; + *xs = Sk4s::Min(fXCap, modX); + assertTiled(*xs, fXMax); + } + + template<typename Next> + bool maybeProcessSpan(Span originalSpan, Next* next) { + SkASSERT(!originalSpan.isEmpty()); + SkPoint start; SkScalar length; int count; + std::tie(start, length, count) = originalSpan; + // Make x and y in range on the tile. + SkScalar x = tile_mod(X(start), fXMax, fXCap); + SkScalar y = Y(start); + + // No need trying to go fast because the steps are larger than a tile or there is one point. + if (fXMax == 1 || count <= 1) { + return false; + } + + // x should be on the tile. + SkASSERT(0.0f <= x && x < fXMax); + Span span({x, y}, length, count); + + if (SkScalarFloorToScalar(x) != 0.0f) { + Span toDraw = span.breakAt(fXMax, 1.0f); + SkASSERT(0.0f <= toDraw.startX() && toDraw.endX() < fXMax); + next->pointSpan(toDraw); + span.offset(-fXMax); + } + + // All of the span could have been on the first tile. If so, then no work to do. + if (span.isEmpty()) return true; + + // At this point the span should be aligned to zero. + SkASSERT(SkScalarFloorToScalar(span.startX()) == 0.0f); + + // Note: The span length has an unintuitive relation to the tile width. The tile width is + // a half open interval [tb, te), but the span is a closed interval [sb, se]. In order to + // compare the two, you need to convert the span to a half open interval. This is done by + // adding dx to se. So, the span becomes: [sb, se + dx). Hence the + 1.0f below. + SkScalar div = (span.length() + 1.0f) / fXMax; + int32_t repeatCount = SkScalarFloorToInt(div); + Span repeatableSpan{{0.0f, y}, fXMax - 1.0f, SkScalarFloorToInt(fXMax)}; + + // Repeat the center section. + SkASSERT(0.0f <= repeatableSpan.startX() && repeatableSpan.endX() < fXMax); + if (repeatCount > 0) { + next->repeatSpan(repeatableSpan, repeatCount); + } + + // Calculate the advance past the center portion. + SkScalar advance = SkScalar(repeatCount) * fXMax; + + // There may be some of the span left over. + span.breakAt(advance, 1.0f); + + // All on a single tile. + if (!span.isEmpty()) { + span.offset(-advance); + SkASSERT(0.0f <= span.startX() && span.endX() < fXMax); + next->pointSpan(span); + } + + return true; + } + +private: + const SkScalar fXMax; + const SkScalar fXCap; + const SkScalar fXInvMax; +}; + +class YRepeatStrategy { +public: + YRepeatStrategy(int32_t max) + : fYMax{SkScalar(max)} + , fYCap{SkScalar(nextafterf(SkScalar(max), 0.0f))} + , fYsInvMax{1.0f / SkScalar(max)} { } + + void tileYPoints(Sk4s* ys) { + Sk4s divY = *ys * fYsInvMax; + Sk4s modY = *ys - divY.floor() * fYMax; + *ys = Sk4s::Min(fYCap, modY); + assertTiled(*ys, fYMax); + } + + SkScalar tileY(SkScalar y) { + SkScalar answer = tile_mod(y, fYMax, fYCap); + SkASSERT(0 <= answer && answer < fYMax); + return answer; + } + +private: + const SkScalar fYMax; + const SkScalar fYCap; + const SkScalar fYsInvMax; +}; +// max = 40 +// mq2[x_] := Abs[(x - 40) - Floor[(x - 40)/80] * 80 - 40] +class XMirrorStrategy { +public: + XMirrorStrategy(int32_t max) + : fXMax{SkScalar(max)} + , fXCap{SkScalar(nextafterf(SkScalar(max), 0.0f))} + , fXDoubleInvMax{1.0f / (2.0f * SkScalar(max))} { } + + void tileXPoints(Sk4s* xs) { + Sk4f bias = *xs - fXMax; + Sk4f div = bias * fXDoubleInvMax; + Sk4f mod = bias - div.floor() * 2.0f * fXMax; + Sk4f unbias = mod - fXMax; + *xs = Sk4f::Min(unbias.abs(), fXCap); + assertTiled(*xs, fXMax); + } + + template <typename Next> + bool maybeProcessSpan(Span originalSpan, Next* next) { return false; } + +private: + SkScalar fXMax; + SkScalar fXCap; + SkScalar fXDoubleInvMax; +}; + +class YMirrorStrategy { +public: + YMirrorStrategy(int32_t max) + : fYMax{SkScalar(max)} + , fYCap{nextafterf(SkScalar(max), 0.0f)} + , fYDoubleInvMax{1.0f / (2.0f * SkScalar(max))} { } + + void tileYPoints(Sk4s* ys) { + Sk4f bias = *ys - fYMax; + Sk4f div = bias * fYDoubleInvMax; + Sk4f mod = bias - div.floor() * 2.0f * fYMax; + Sk4f unbias = mod - fYMax; + *ys = Sk4f::Min(unbias.abs(), fYCap); + assertTiled(*ys, fYMax); + } + + SkScalar tileY(SkScalar y) { + SkScalar bias = y - fYMax; + SkScalar div = bias * fYDoubleInvMax; + SkScalar mod = bias - SkScalarFloorToScalar(div) * 2.0f * fYMax; + SkScalar unbias = mod - fYMax; + SkScalar answer = SkMinScalar(SkScalarAbs(unbias), fYCap); + SkASSERT(0 <= answer && answer < fYMax); + return answer; + } + +private: + SkScalar fYMax; + SkScalar fYCap; + SkScalar fYDoubleInvMax; +}; + +} // namespace +#endif // SkLinearBitmapPipeline_tile_DEFINED |