/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkLinearBitmapPipeline.h" #include #include #include #include #include "SkArenaAlloc.h" #include "SkLinearBitmapPipeline_core.h" #include "SkLinearBitmapPipeline_matrix.h" #include "SkLinearBitmapPipeline_tile.h" #include "SkLinearBitmapPipeline_sample.h" #include "SkNx.h" #include "SkOpts.h" #include "SkPM4f.h" namespace { //////////////////////////////////////////////////////////////////////////////////////////////////// // Matrix Stage // PointProcessor uses a strategy to help complete the work of the different stages. The strategy // must implement the following methods: // * processPoints(xs, ys) - must mutate the xs and ys for the stage. // * maybeProcessSpan(span, next) - This represents a horizontal series of pixels // to work over. // span - encapsulation of span. // next - a pointer to the next stage. // maybeProcessSpan - returns false if it can not process the span and needs to fallback to // point lists for processing. template class MatrixStage final : public SkLinearBitmapPipeline::PointProcessorInterface { public: template MatrixStage(Next* next, Args&&... args) : fNext{next} , fStrategy{std::forward(args)...}{ } MatrixStage(Next* next, MatrixStage* stage) : fNext{next} , fStrategy{stage->fStrategy} { } void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override { fStrategy.processPoints(&xs, &ys); fNext->pointListFew(n, xs, ys); } void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override { fStrategy.processPoints(&xs, &ys); fNext->pointList4(xs, ys); } // The span you pass must not be empty. void pointSpan(Span span) override { SkASSERT(!span.isEmpty()); if (!fStrategy.maybeProcessSpan(span, fNext)) { span_fallback(span, this); } } private: Next* const fNext; Strategy fStrategy; }; template using TranslateMatrix = MatrixStage; template using ScaleMatrix = MatrixStage; template using AffineMatrix = MatrixStage; template using PerspectiveMatrix = MatrixStage; //////////////////////////////////////////////////////////////////////////////////////////////////// // Tile Stage template class CombinedTileStage final : public SkLinearBitmapPipeline::PointProcessorInterface { public: CombinedTileStage(Next* next, SkISize dimensions) : fNext{next} , fXStrategy{dimensions.width()} , fYStrategy{dimensions.height()}{ } CombinedTileStage(Next* next, CombinedTileStage* stage) : fNext{next} , fXStrategy{stage->fXStrategy} , fYStrategy{stage->fYStrategy} { } void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override { fXStrategy.tileXPoints(&xs); fYStrategy.tileYPoints(&ys); fNext->pointListFew(n, xs, ys); } void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override { fXStrategy.tileXPoints(&xs); fYStrategy.tileYPoints(&ys); fNext->pointList4(xs, ys); } // The span you pass must not be empty. void pointSpan(Span span) override { SkASSERT(!span.isEmpty()); SkPoint start; SkScalar length; int count; std::tie(start, length, count) = span; if (span.count() == 1) { // DANGER: // The explicit casts from float to Sk4f are not usually necessary, but are here to // work around an MSVC 2015u2 c++ code generation bug. This is tracked using skia bug // 5566. this->pointListFew(1, Sk4f{span.startX()}, Sk4f{span.startY()}); return; } SkScalar x = X(start); SkScalar y = fYStrategy.tileY(Y(start)); Span yAdjustedSpan{{x, y}, length, count}; if (!fXStrategy.maybeProcessSpan(yAdjustedSpan, fNext)) { span_fallback(span, this); } } private: Next* const fNext; XStrategy fXStrategy; YStrategy fYStrategy; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Specialized Samplers // RGBA8888UnitRepeatSrc - A sampler that takes advantage of the fact the the src and destination // are the same format and do not need in transformations in pixel space. Therefore, there is no // need to convert them to HiFi pixel format. class RGBA8888UnitRepeatSrc final : public SkLinearBitmapPipeline::SampleProcessorInterface, public SkLinearBitmapPipeline::DestinationInterface { public: RGBA8888UnitRepeatSrc(const uint32_t* src, int32_t width) : fSrc{src}, fWidth{width} { } void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override { SkASSERT(fDest + n <= fEnd); // At this point xs and ys should be >= 0, so trunc is the same as floor. Sk4i iXs = SkNx_cast(xs); Sk4i iYs = SkNx_cast(ys); if (n >= 1) *fDest++ = *this->pixelAddress(iXs[0], iYs[0]); if (n >= 2) *fDest++ = *this->pixelAddress(iXs[1], iYs[1]); if (n >= 3) *fDest++ = *this->pixelAddress(iXs[2], iYs[2]); } void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override { SkASSERT(fDest + 4 <= fEnd); Sk4i iXs = SkNx_cast(xs); Sk4i iYs = SkNx_cast(ys); *fDest++ = *this->pixelAddress(iXs[0], iYs[0]); *fDest++ = *this->pixelAddress(iXs[1], iYs[1]); *fDest++ = *this->pixelAddress(iXs[2], iYs[2]); *fDest++ = *this->pixelAddress(iXs[3], iYs[3]); } void pointSpan(Span span) override { SkASSERT(fDest + span.count() <= fEnd); if (span.length() != 0.0f) { int32_t x = SkScalarTruncToInt(span.startX()); int32_t y = SkScalarTruncToInt(span.startY()); const uint32_t* src = this->pixelAddress(x, y); memmove(fDest, src, span.count() * sizeof(uint32_t)); fDest += span.count(); } } void repeatSpan(Span span, int32_t repeatCount) override { SkASSERT(fDest + span.count() * repeatCount <= fEnd); int32_t x = SkScalarTruncToInt(span.startX()); int32_t y = SkScalarTruncToInt(span.startY()); const uint32_t* src = this->pixelAddress(x, y); uint32_t* dest = fDest; while (repeatCount --> 0) { memmove(dest, src, span.count() * sizeof(uint32_t)); dest += span.count(); } fDest = dest; } void setDestination(void* dst, int count) override { fDest = static_cast(dst); fEnd = fDest + count; } private: const uint32_t* pixelAddress(int32_t x, int32_t y) { return &fSrc[fWidth * y + x]; } const uint32_t* const fSrc; const int32_t fWidth; uint32_t* fDest; uint32_t* fEnd; }; // RGBA8888UnitRepeatSrc - A sampler that takes advantage of the fact the the src and destination // are the same format and do not need in transformations in pixel space. Therefore, there is no // need to convert them to HiFi pixel format. class RGBA8888UnitRepeatSrcOver final : public SkLinearBitmapPipeline::SampleProcessorInterface, public SkLinearBitmapPipeline::DestinationInterface { public: RGBA8888UnitRepeatSrcOver(const uint32_t* src, int32_t width) : fSrc{src}, fWidth{width} { } void SK_VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) override { SkASSERT(fDest + n <= fEnd); // At this point xs and ys should be >= 0, so trunc is the same as floor. Sk4i iXs = SkNx_cast(xs); Sk4i iYs = SkNx_cast(ys); if (n >= 1) blendPixelAt(iXs[0], iYs[0]); if (n >= 2) blendPixelAt(iXs[1], iYs[1]); if (n >= 3) blendPixelAt(iXs[2], iYs[2]); } void SK_VECTORCALL pointList4(Sk4s xs, Sk4s ys) override { SkASSERT(fDest + 4 <= fEnd); Sk4i iXs = SkNx_cast(xs); Sk4i iYs = SkNx_cast(ys); blendPixelAt(iXs[0], iYs[0]); blendPixelAt(iXs[1], iYs[1]); blendPixelAt(iXs[2], iYs[2]); blendPixelAt(iXs[3], iYs[3]); } void pointSpan(Span span) override { if (span.length() != 0.0f) { this->repeatSpan(span, 1); } } void repeatSpan(Span span, int32_t repeatCount) override { SkASSERT(fDest + span.count() * repeatCount <= fEnd); SkASSERT(span.count() > 0); SkASSERT(repeatCount > 0); int32_t x = (int32_t)span.startX(); int32_t y = (int32_t)span.startY(); const uint32_t* beginSpan = this->pixelAddress(x, y); SkOpts::srcover_srgb_srgb(fDest, beginSpan, span.count() * repeatCount, span.count()); fDest += span.count() * repeatCount; SkASSERT(fDest <= fEnd); } void setDestination(void* dst, int count) override { SkASSERT(count > 0); fDest = static_cast(dst); fEnd = fDest + count; } private: const uint32_t* pixelAddress(int32_t x, int32_t y) { return &fSrc[fWidth * y + x]; } void blendPixelAt(int32_t x, int32_t y) { const uint32_t* src = this->pixelAddress(x, y); SkOpts::srcover_srgb_srgb(fDest, src, 1, 1); fDest += 1; } const uint32_t* const fSrc; const int32_t fWidth; uint32_t* fDest; uint32_t* fEnd; }; using Blender = SkLinearBitmapPipeline::BlendProcessorInterface; //////////////////////////////////////////////////////////////////////////////////////////////////// // Pixel Blender Stage template class SrcFPPixel final : public Blender { public: SrcFPPixel(float postAlpha) : fPostAlpha{postAlpha} { } SrcFPPixel(const SrcFPPixel& Blender) : fPostAlpha(Blender.fPostAlpha) {} void SK_VECTORCALL blendPixel(Sk4f pixel) override { SkASSERT(fDst + 1 <= fEnd ); this->srcPixel(fDst, pixel, 0); fDst += 1; } void SK_VECTORCALL blend4Pixels(Sk4f p0, Sk4f p1, Sk4f p2, Sk4f p3) override { SkASSERT(fDst + 4 <= fEnd); SkPM4f* dst = fDst; this->srcPixel(dst, p0, 0); this->srcPixel(dst, p1, 1); this->srcPixel(dst, p2, 2); this->srcPixel(dst, p3, 3); fDst += 4; } void setDestination(void* dst, int count) override { fDst = static_cast(dst); fEnd = fDst + count; } private: void SK_VECTORCALL srcPixel(SkPM4f* dst, Sk4f pixel, int index) { check_pixel(pixel); Sk4f newPixel = pixel; if (alphaType == kUnpremul_SkAlphaType) { newPixel = Premultiply(pixel); } newPixel = newPixel * fPostAlpha; newPixel.store(dst + index); } static Sk4f SK_VECTORCALL Premultiply(Sk4f pixel) { float alpha = pixel[3]; return pixel * Sk4f{alpha, alpha, alpha, 1.0f}; } SkPM4f* fDst; SkPM4f* fEnd; float fPostAlpha; }; } // namespace //////////////////////////////////////////////////////////////////////////////////////////////////// // SkLinearBitmapPipeline SkLinearBitmapPipeline::~SkLinearBitmapPipeline() {} SkLinearBitmapPipeline::SkLinearBitmapPipeline( const SkMatrix& inverse, SkFilterQuality filterQuality, SkShader::TileMode xTile, SkShader::TileMode yTile, SkColor paintColor, const SkPixmap& srcPixmap, SkArenaAlloc* allocator) { SkISize dimensions = srcPixmap.info().dimensions(); const SkImageInfo& srcImageInfo = srcPixmap.info(); SkMatrix adjustedInverse = inverse; if (filterQuality == kNone_SkFilterQuality) { if (inverse.getScaleX() >= 0.0f) { adjustedInverse.setTranslateX( nextafterf(inverse.getTranslateX(), std::floor(inverse.getTranslateX()))); } if (inverse.getScaleY() >= 0.0f) { adjustedInverse.setTranslateY( nextafterf(inverse.getTranslateY(), std::floor(inverse.getTranslateY()))); } } SkScalar dx = adjustedInverse.getScaleX(); // If it is an index 8 color type, the sampler converts to unpremul for better fidelity. SkAlphaType alphaType = srcImageInfo.alphaType(); if (srcPixmap.colorType() == kIndex_8_SkColorType) { alphaType = kUnpremul_SkAlphaType; } float postAlpha = SkColorGetA(paintColor) * (1.0f / 255.0f); // As the stages are built, the chooser function may skip a stage. For example, with the // identity matrix, the matrix stage is skipped, and the tilerStage is the first stage. auto blenderStage = this->chooseBlenderForShading(alphaType, postAlpha, allocator); auto samplerStage = this->chooseSampler( blenderStage, filterQuality, xTile, yTile, srcPixmap, paintColor, allocator); auto tilerStage = this->chooseTiler( samplerStage, dimensions, xTile, yTile, filterQuality, dx, allocator); fFirstStage = this->chooseMatrix(tilerStage, adjustedInverse, allocator); fLastStage = blenderStage; } SkLinearBitmapPipeline::SkLinearBitmapPipeline( const SkLinearBitmapPipeline& pipeline, const SkPixmap& srcPixmap, SkBlendMode mode, const SkImageInfo& dstInfo, SkArenaAlloc* allocator) { SkASSERT(mode == SkBlendMode::kSrc || mode == SkBlendMode::kSrcOver); SkASSERT(srcPixmap.info().colorType() == dstInfo.colorType() && srcPixmap.info().colorType() == kRGBA_8888_SkColorType); SampleProcessorInterface* sampleStage; if (mode == SkBlendMode::kSrc) { auto sampler = allocator->make( srcPixmap.writable_addr32(0, 0), srcPixmap.rowBytes() / 4); sampleStage = sampler; fLastStage = sampler; } else { auto sampler = allocator->make( srcPixmap.writable_addr32(0, 0), srcPixmap.rowBytes() / 4); sampleStage = sampler; fLastStage = sampler; } auto tilerStage = pipeline.fTileStageCloner(sampleStage, allocator); auto matrixStage = pipeline.fMatrixStageCloner(tilerStage, allocator); fFirstStage = matrixStage; } SkLinearBitmapPipeline* SkLinearBitmapPipeline::ClonePipelineForBlitting( const SkLinearBitmapPipeline& pipeline, SkMatrix::TypeMask matrixMask, SkFilterQuality filterQuality, const SkPixmap& srcPixmap, float finalAlpha, SkBlendMode blendMode, const SkImageInfo& dstInfo, SkArenaAlloc* allocator) { if (blendMode == SkBlendMode::kSrcOver && srcPixmap.info().alphaType() == kOpaque_SkAlphaType) { blendMode = SkBlendMode::kSrc; } if (matrixMask & ~SkMatrix::kTranslate_Mask ) { return nullptr; } if (filterQuality != SkFilterQuality::kNone_SkFilterQuality) { return nullptr; } if (finalAlpha != 1.0f) { return nullptr; } if (srcPixmap.info().colorType() != kRGBA_8888_SkColorType || dstInfo.colorType() != kRGBA_8888_SkColorType) { return nullptr; } if (!srcPixmap.info().gammaCloseToSRGB() || !dstInfo.gammaCloseToSRGB()) { return nullptr; } if (blendMode != SkBlendMode::kSrc && blendMode != SkBlendMode::kSrcOver) { return nullptr; } return allocator->make( pipeline, srcPixmap, blendMode, dstInfo, allocator); } void SkLinearBitmapPipeline::shadeSpan4f(int x, int y, SkPM4f* dst, int count) { SkASSERT(count > 0); this->blitSpan(x, y, dst, count); } void SkLinearBitmapPipeline::blitSpan(int x, int y, void* dst, int count) { SkASSERT(count > 0); fLastStage->setDestination(dst, count); // The count and length arguments start out in a precise relation in order to keep the // math correct through the different stages. Count is the number of pixel to produce. // Since the code samples at pixel centers, length is the distance from the center of the // first pixel to the center of the last pixel. This implies that length is count-1. fFirstStage->pointSpan(Span{{x + 0.5f, y + 0.5f}, count - 1.0f, count}); } SkLinearBitmapPipeline::PointProcessorInterface* SkLinearBitmapPipeline::chooseMatrix( PointProcessorInterface* next, const SkMatrix& inverse, SkArenaAlloc* allocator) { if (inverse.hasPerspective()) { auto matrixStage = allocator->make>( next, SkVector{inverse.getTranslateX(), inverse.getTranslateY()}, SkVector{inverse.getScaleX(), inverse.getScaleY()}, SkVector{inverse.getSkewX(), inverse.getSkewY()}, SkVector{inverse.getPerspX(), inverse.getPerspY()}, inverse.get(SkMatrix::kMPersp2)); fMatrixStageCloner = [matrixStage](PointProcessorInterface* cloneNext, SkArenaAlloc* memory) { return memory->make>(cloneNext, matrixStage); }; return matrixStage; } else if (inverse.getSkewX() != 0.0f || inverse.getSkewY() != 0.0f) { auto matrixStage = allocator->make>( next, SkVector{inverse.getTranslateX(), inverse.getTranslateY()}, SkVector{inverse.getScaleX(), inverse.getScaleY()}, SkVector{inverse.getSkewX(), inverse.getSkewY()}); fMatrixStageCloner = [matrixStage](PointProcessorInterface* cloneNext, SkArenaAlloc* memory) { return memory->make>(cloneNext, matrixStage); }; return matrixStage; } else if (inverse.getScaleX() != 1.0f || inverse.getScaleY() != 1.0f) { auto matrixStage = allocator->make>( next, SkVector{inverse.getTranslateX(), inverse.getTranslateY()}, SkVector{inverse.getScaleX(), inverse.getScaleY()}); fMatrixStageCloner = [matrixStage](PointProcessorInterface* cloneNext, SkArenaAlloc* memory) { return memory->make>(cloneNext, matrixStage); }; return matrixStage; } else if (inverse.getTranslateX() != 0.0f || inverse.getTranslateY() != 0.0f) { auto matrixStage = allocator->make>( next, SkVector{inverse.getTranslateX(), inverse.getTranslateY()}); fMatrixStageCloner = [matrixStage](PointProcessorInterface* cloneNext, SkArenaAlloc* memory) { return memory->make>(cloneNext, matrixStage); }; return matrixStage; } else { fMatrixStageCloner = [](PointProcessorInterface* cloneNext, SkArenaAlloc* memory) { return cloneNext; }; return next; } } template SkLinearBitmapPipeline::PointProcessorInterface* SkLinearBitmapPipeline::createTiler( SampleProcessorInterface* next, SkISize dimensions, SkArenaAlloc* allocator) { auto tilerStage = allocator->make(next, dimensions); fTileStageCloner = [tilerStage](SampleProcessorInterface* cloneNext, SkArenaAlloc* memory) -> PointProcessorInterface* { return memory->make(cloneNext, tilerStage); }; return tilerStage; } template SkLinearBitmapPipeline::PointProcessorInterface* SkLinearBitmapPipeline::chooseTilerYMode( SampleProcessorInterface* next, SkShader::TileMode yMode, SkISize dimensions, SkArenaAlloc* allocator) { switch (yMode) { case SkShader::kClamp_TileMode: { using Tiler = CombinedTileStage; return this->createTiler(next, dimensions, allocator); } case SkShader::kRepeat_TileMode: { using Tiler = CombinedTileStage; return this->createTiler(next, dimensions, allocator); } case SkShader::kMirror_TileMode: { using Tiler = CombinedTileStage; return this->createTiler(next, dimensions, allocator); } } // Should never get here. SkFAIL("Not all Y tile cases covered."); return nullptr; } SkLinearBitmapPipeline::PointProcessorInterface* SkLinearBitmapPipeline::chooseTiler( SampleProcessorInterface* next, SkISize dimensions, SkShader::TileMode xMode, SkShader::TileMode yMode, SkFilterQuality filterQuality, SkScalar dx, SkArenaAlloc* allocator) { switch (xMode) { case SkShader::kClamp_TileMode: return this->chooseTilerYMode(next, yMode, dimensions, allocator); case SkShader::kRepeat_TileMode: if (dx == 1.0f && filterQuality == kNone_SkFilterQuality) { return this->chooseTilerYMode( next, yMode, dimensions, allocator); } else { return this->chooseTilerYMode( next, yMode, dimensions, allocator); } case SkShader::kMirror_TileMode: return this->chooseTilerYMode(next, yMode, dimensions, allocator); } // Should never get here. SkFAIL("Not all X tile cases covered."); return nullptr; } template SkLinearBitmapPipeline::PixelAccessorInterface* SkLinearBitmapPipeline::chooseSpecificAccessor( const SkPixmap& srcPixmap, SkArenaAlloc* allocator) { if (srcPixmap.info().gammaCloseToSRGB()) { using Accessor = PixelAccessor; return allocator->make(srcPixmap); } else { using Accessor = PixelAccessor; return allocator->make(srcPixmap); } } SkLinearBitmapPipeline::PixelAccessorInterface* SkLinearBitmapPipeline::choosePixelAccessor( const SkPixmap& srcPixmap, const SkColor A8TintColor, SkArenaAlloc* allocator) { const SkImageInfo& imageInfo = srcPixmap.info(); switch (imageInfo.colorType()) { case kAlpha_8_SkColorType: { using Accessor = PixelAccessor; return allocator->make(srcPixmap, A8TintColor); } case kARGB_4444_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kRGB_565_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kRGBA_8888_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kBGRA_8888_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kIndex_8_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kGray_8_SkColorType: return this->chooseSpecificAccessor(srcPixmap, allocator); case kRGBA_F16_SkColorType: { using Accessor = PixelAccessor; return allocator->make(srcPixmap); } default: // Should never get here. SkFAIL("Pixel source not supported."); return nullptr; } } SkLinearBitmapPipeline::SampleProcessorInterface* SkLinearBitmapPipeline::chooseSampler( Blender* next, SkFilterQuality filterQuality, SkShader::TileMode xTile, SkShader::TileMode yTile, const SkPixmap& srcPixmap, const SkColor A8TintColor, SkArenaAlloc* allocator) { const SkImageInfo& imageInfo = srcPixmap.info(); SkISize dimensions = imageInfo.dimensions(); // Special case samplers with fully expanded templates if (imageInfo.gammaCloseToSRGB()) { if (filterQuality == kNone_SkFilterQuality) { switch (imageInfo.colorType()) { case kN32_SkColorType: { using Sampler = NearestNeighborSampler< PixelAccessor, Blender>; return allocator->make(next, srcPixmap); } case kIndex_8_SkColorType: { using Sampler = NearestNeighborSampler< PixelAccessor, Blender>; return allocator->make(next, srcPixmap); } default: break; } } else { switch (imageInfo.colorType()) { case kN32_SkColorType: { using Sampler = BilerpSampler< PixelAccessor, Blender>; return allocator->make(next, dimensions, xTile, yTile, srcPixmap); } case kIndex_8_SkColorType: { using Sampler = BilerpSampler< PixelAccessor, Blender>; return allocator->make(next, dimensions, xTile, yTile, srcPixmap); } default: break; } } } auto pixelAccessor = this->choosePixelAccessor(srcPixmap, A8TintColor, allocator); // General cases. if (filterQuality == kNone_SkFilterQuality) { using Sampler = NearestNeighborSampler; return allocator->make(next, pixelAccessor); } else { using Sampler = BilerpSampler; return allocator->make(next, dimensions, xTile, yTile, pixelAccessor); } } Blender* SkLinearBitmapPipeline::chooseBlenderForShading( SkAlphaType alphaType, float postAlpha, SkArenaAlloc* allocator) { if (alphaType == kUnpremul_SkAlphaType) { return allocator->make>(postAlpha); } else { // kOpaque_SkAlphaType is treated the same as kPremul_SkAlphaType return allocator->make>(postAlpha); } }