/* * 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_core_DEFINED #define SkLinearBitmapPipeline_core_DEFINED #include #include #include "SkNx.h" // New bilerp strategy: // Pass through on bilerpList4 and bilerpListFew (analogs to pointList), introduce bilerpEdge // which takes 4 points. If the sample spans an edge, then break it into a bilerpEdge. Bilerp // span then becomes a normal span except in special cases where an extra Y is given. The bilerp // need to stay single point calculations until the tile layer. // TODO: // - edge span predicate. // - introduce new point API // - Add tile for new api. namespace { struct X { explicit X(SkScalar val) : fVal{val} { } explicit X(SkPoint pt) : fVal{pt.fX} { } explicit X(SkSize s) : fVal{s.fWidth} { } explicit X(SkISize s) : fVal((SkScalar)s.fWidth) { } operator SkScalar () const {return fVal;} private: SkScalar fVal; }; struct Y { explicit Y(SkScalar val) : fVal{val} { } explicit Y(SkPoint pt) : fVal{pt.fY} { } explicit Y(SkSize s) : fVal{s.fHeight} { } explicit Y(SkISize s) : fVal((SkScalar)s.fHeight) { } operator SkScalar () const {return fVal;} private: SkScalar fVal; }; // The Span class enables efficient processing horizontal spans of pixels. // * start - the point where to start the span. // * length - the number of pixels to traverse in source space. // * count - the number of pixels to produce in destination space. // Both start and length are mapped through the inversion matrix to produce values in source // space. After the matrix operation, the tilers may break the spans up into smaller spans. // The tilers can produce spans that seem nonsensical. // * The clamp tiler can create spans with length of 0. This indicates to copy an edge pixel out // to the edge of the destination scan. // * The mirror tiler can produce spans with negative length. This indicates that the source // should be traversed in the opposite direction to the destination pixels. class Span { public: Span(SkPoint start, SkScalar length, int count) : fStart(start) , fLength(length) , fCount{count} { SkASSERT(std::isfinite(length)); } operator std::tuple() { return std::tie(fStart, fLength, fCount); } bool isEmpty() const { return 0 == fCount; } void clear() { fCount = 0; } int count() const { return fCount; } SkScalar length() const { return fLength; } SkScalar startX() const { return X(fStart); } SkScalar endX() const { return this->startX() + this->length(); } SkScalar startY() const { return Y(fStart); } Span emptySpan() { return Span{{0.0, 0.0}, 0.0f, 0}; } bool completelyWithin(SkScalar xMin, SkScalar xMax) const { SkScalar sMin, sMax; std::tie(sMin, sMax) = std::minmax(startX(), endX()); return xMin <= sMin && sMax < xMax; } void offset(SkScalar offsetX) { fStart.offset(offsetX, 0.0f); } Span breakAt(SkScalar breakX, SkScalar dx) { SkASSERT(std::isfinite(breakX)); SkASSERT(std::isfinite(dx)); SkASSERT(dx != 0.0f); if (this->isEmpty()) { return this->emptySpan(); } int dxSteps = SkScalarFloorToInt((breakX - this->startX()) / dx); if (dxSteps < 0) { // The span is wholly after breakX. return this->emptySpan(); } else if (dxSteps >= fCount) { // The span is wholly before breakX. Span answer = *this; this->clear(); return answer; } // Calculate the values for the span to cleave off. SkScalar newLength = dxSteps * dx; // If the last (or first if count = 1) sample lands directly on the boundary. Include it // when dx < 0 and exclude it when dx > 0. // Reasoning: // dx > 0: The sample point on the boundary is part of the next span because the entire // pixel is after the boundary. // dx < 0: The sample point on the boundary is part of the current span because the // entire pixel is before the boundary. if (this->startX() + newLength == breakX && dx > 0) { if (dxSteps > 0) { dxSteps -= 1; newLength -= dx; } else { return this->emptySpan(); } } // Calculate new span parameters SkPoint newStart = fStart; int newCount = dxSteps + 1; SkASSERT(newCount > 0); // Update this span to reflect the break. SkScalar lengthToStart = newLength + dx; fLength -= lengthToStart; fCount -= newCount; fStart = {this->startX() + lengthToStart, Y(fStart)}; return Span{newStart, newLength, newCount}; } void clampToSinglePixel(SkPoint pixel) { fStart = pixel; fLength = 0.0f; } private: SkPoint fStart; SkScalar fLength; int fCount; }; template void span_fallback(Span span, Stage* stage) { SkPoint start; SkScalar length; int count; std::tie(start, length, count) = span; Sk4f startXs{X(start)}; Sk4f ys{Y(start)}; Sk4f mults = {0.0f, 1.0f, 2.0f, 3.0f}; // Initializing this is not needed, but some compilers can't figure this out. Sk4s dXs{0.0f}; if (count > 1) { SkScalar dx = length / (count - 1); dXs = Sk4f{dx}; } // Instead of using xs = xs + dx every round, this uses xs = i * dx + X(start). This // eliminates the rounding error for the sum. Sk4f xs = startXs + mults * dXs; while (count >= 4) { stage->pointList4(xs, ys); mults += Sk4f{4.0f}; xs = mults * dXs + startXs; count -= 4; } if (count > 0) { stage->pointListFew(count, xs, ys); } } inline Sk4f SK_VECTORCALL check_pixel(const Sk4f& pixel) { SkASSERTF(0.0f <= pixel[0] && pixel[0] <= 1.0f, "pixel[0]: %f", pixel[0]); SkASSERTF(0.0f <= pixel[1] && pixel[1] <= 1.0f, "pixel[1]: %f", pixel[1]); SkASSERTF(0.0f <= pixel[2] && pixel[2] <= 1.0f, "pixel[2]: %f", pixel[2]); SkASSERTF(0.0f <= pixel[3] && pixel[3] <= 1.0f, "pixel[3]: %f", pixel[3]); return pixel; } } // namespace class SkLinearBitmapPipeline::PointProcessorInterface { public: virtual ~PointProcessorInterface() { } // Take the first n (where 0 < n && n < 4) items from xs and ys and sample those points. For // nearest neighbor, that means just taking the floor xs and ys. For bilerp, this means // to expand the bilerp filter around the point and sample using that filter. virtual void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) = 0; // Same as pointListFew, but n = 4. virtual void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) = 0; // A span is a compact form of sample points that are obtained by mapping points from // destination space to source space. This is used for horizontal lines only, and is mainly // used to take advantage of memory coherence for horizontal spans. virtual void pointSpan(Span span) = 0; }; class SkLinearBitmapPipeline::SampleProcessorInterface : public SkLinearBitmapPipeline::PointProcessorInterface { public: // Used for nearest neighbor when scale factor is 1.0. The span can just be repeated with no // edge pixel alignment problems. This is for handling a very common case. virtual void repeatSpan(Span span, int32_t repeatCount) = 0; }; class SkLinearBitmapPipeline::DestinationInterface { public: virtual ~DestinationInterface() { } // Count is normally not needed, but in these early stages of development it is useful to // check bounds. // TODO(herb): 4/6/2016 - remove count when code is stable. virtual void setDestination(void* dst, int count) = 0; }; class SkLinearBitmapPipeline::BlendProcessorInterface : public SkLinearBitmapPipeline::DestinationInterface { public: virtual void SK_VECTORCALL blendPixel(Sk4f pixel0) = 0; virtual void SK_VECTORCALL blend4Pixels(Sk4f p0, Sk4f p1, Sk4f p2, Sk4f p3) = 0; }; class SkLinearBitmapPipeline::PixelAccessorInterface { public: virtual ~PixelAccessorInterface() { } virtual void SK_VECTORCALL getFewPixels( int n, Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2) const = 0; virtual void SK_VECTORCALL get4Pixels( Sk4i xs, Sk4i ys, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const = 0; virtual void get4Pixels( const void* src, int index, Sk4f* px0, Sk4f* px1, Sk4f* px2, Sk4f* px3) const = 0; virtual Sk4f getPixelFromRow(const void* row, int index) const = 0; virtual Sk4f getPixelAt(int index) const = 0; virtual const void* row(int y) const = 0; }; #endif // SkLinearBitmapPipeline_core_DEFINED