void FindCanvas::findHelper(const void* text, size_t byteLength, const SkPaint& paint, const SkScalar positions[], SkScalar y, SkRect (FindCanvas::*addMatch)(int index, const SkPaint& paint, int count, const uint16_t* glyphs, const SkScalar positions[], SkScalar y)) { //SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); SkASSERT(mMatches); GlyphSet* glyphSet = getGlyphs(paint); const int count = glyphSet->getCount(); int numCharacters = byteLength >> 1; const uint16_t* chars = (const uint16_t*) text; // This block will check to see if we are continuing from another line. If // so, the user needs to have added a space, which we do not draw. if (mWorkingIndex) { SkPoint newY; getTotalMatrix().mapXY(0, y, &newY); SkIRect workingBounds = mWorkingRegion.getBounds(); int newYInt = SkScalarRound(newY.fY); if (workingBounds.fTop > newYInt) { // The new text is above the working region, so we know it's not // a continuation. resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); } else if (workingBounds.fBottom < newYInt) { // Now we know that this line is lower than our partial match. SkPaint clonePaint(paint); clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); uint16_t space; clonePaint.textToGlyphs(" ", 1, &space); if (glyphSet->characterMatches(space, mWorkingIndex)) { mWorkingIndex++; if (mWorkingIndex == count) { // We already know that it is not clipped out because we // checked for that before saving the working region. insertMatchInfo(mWorkingRegion); resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); // We have found a match, so continue on this line from // scratch. } } else { resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); } } // If neither one is true, then we are likely continuing on the same // line, but are in a new draw call because the paint has changed. In // this case, we can continue without adding a space. } // j is the position in the search text // Start off with mWorkingIndex in case we are continuing from a prior call int j = mWorkingIndex; // index is the position in the drawn text int index = 0; for ( ; index != numCharacters; index++) { if (glyphSet->characterMatches(chars[index], j)) { // The jth character in the search text matches the indexth position // in the drawn text, so increase j. j++; if (j != count) { continue; } // The last count characters match, so we found the entire // search string. int remaining = count - mWorkingIndex; int matchIndex = index - remaining + 1; // Set up a pointer to the matching text in 'chars'. const uint16_t* glyphs = chars + matchIndex; SkRect rect = (this->*addMatch)(matchIndex, paint, remaining, glyphs, positions, y); // We need an SkIRect for SkRegion operations. SkIRect iRect; rect.roundOut(&iRect); // If the rectangle is partially clipped, assume that the text is // not visible, so skip this match. if (getTotalClip().contains(iRect)) { // Want to outset the drawn rectangle by the same amount as // mOutset iRect.inset(-INTEGER_OUTSET, -INTEGER_OUTSET); SkRegion regionToAdd(iRect); if (!mWorkingRegion.isEmpty()) { // If this is on the same line as our working region, make // sure that they are close enough together that they are // supposed to be part of the same text string. // The width of two spaces has arbitrarily been chosen. const SkIRect& workingBounds = mWorkingRegion.getBounds(); if (workingBounds.fTop <= iRect.fBottom && workingBounds.fBottom >= iRect.fTop && SkIntToScalar(iRect.fLeft - workingBounds.fRight) > approximateSpaceWidth(paint)) { index = -1; // Will increase to 0 on next run // In this case, we need to start from the beginning of // the text being searched and our search term. j = 0; mWorkingIndex = 0; mWorkingRegion.setEmpty(); continue; } // Add the mWorkingRegion, which contains rectangles from // the previous line(s). regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); } insertMatchInfo(regionToAdd); #if INCLUDE_SUBSTRING_MATCHES // Reset index to the location of the match and reset j to the // beginning, so that on the next iteration of the loop, index // will advance by 1 and we will compare the next character in // chars to the first character in the GlyphSet. index = matchIndex; #endif } else { // This match was clipped out, so begin looking at the next // character from our hidden match index = matchIndex; } // Whether the clip contained it or not, we need to start over // with our recording canvas resetWorkingCanvas(); } else { // Index needs to be set to index - j + 1. // This is a ridiculous case, but imagine the situation where the // user is looking for the string "jjog" in the drawText call for // "jjjog". The first two letters match. However, when the index // is 2, and we discover that 'o' and 'j' do not match, we should go // back to 1, where we do, in fact, have a match // FIXME: This does not work if (as in our example) "jj" is in one // draw call and "jog" is in the next. Doing so would require a // stack, keeping track of multiple possible working indeces and // regions. This is likely an uncommon case. index = index - j; // index will be increased by one on the next // iteration } // We reach here in one of two cases: // 1) We just completed a match, so any working rectangle/index is no // longer needed, and we will start over from the beginning // 2) The glyphs do not match, so we start over at the beginning of // the search string. j = 0; mWorkingIndex = 0; mWorkingRegion.setEmpty(); } // At this point, we have searched all of the text in the current drawText // call. // Keep track of a partial match that may start on this line. if (j > 0) { // if j is greater than 0, we have a partial match int relativeCount = j - mWorkingIndex; // Number of characters in this // part of the match. int partialIndex = index - relativeCount; // Index that starts our // partial match. const uint16_t* partialGlyphs = chars + partialIndex; SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount, partialGlyphs, positions, y); partial.inset(mOutset, mOutset); SkIRect dest; partial.roundOut(&dest); // Only save a partial if it is in the current clip. if (getTotalClip().contains(dest)) { mWorkingRegion.op(dest, SkRegion::kUnion_Op); mWorkingIndex = j; return; } } // This string doesn't go into the next drawText, so reset our working // variables mWorkingRegion.setEmpty(); mWorkingIndex = 0; }
static SkRect offset_center_to(const SkIRect& src, SkScalar x, SkScalar y) { SkScalar halfW = 0.5f * src.width(); SkScalar halfH = 0.5f * src.height(); return SkRect::MakeLTRB(x - halfW, y - halfH, x + halfW, y + halfH); }
// Atlased layers must be small enough to fit in the atlas, not have a // paint with an image filter and be neither nested nor nesting. // TODO: allow leaf nested layers to appear in the atlas. void GrLayerHoister::FindLayersToAtlas(GrContext* context, const SkPicture* topLevelPicture, const SkMatrix& initialMat, const SkRect& query, SkTDArray<GrHoistedLayer>* atlased, SkTDArray<GrHoistedLayer>* recycled, int numSamples) { if (0 != numSamples) { // MSAA layers are currently never atlased return; } GrLayerCache* layerCache = context->getLayerCache(); layerCache->processDeletedPictures(); const SkBigPicture::AccelData* topLevelData = nullptr; if (const SkBigPicture* bp = topLevelPicture->asSkBigPicture()) { topLevelData = bp->accelData(); } if (!topLevelData) { return; } const SkLayerInfo *topLevelGPUData = static_cast<const SkLayerInfo*>(topLevelData); if (0 == topLevelGPUData->numBlocks()) { return; } atlased->setReserve(atlased->count() + topLevelGPUData->numBlocks()); for (int i = 0; i < topLevelGPUData->numBlocks(); ++i) { const SkLayerInfo::BlockInfo& info = topLevelGPUData->block(i); // TODO: ignore perspective projected layers here? bool disallowAtlasing = info.fHasNestedLayers || info.fIsNested || (info.fPaint && info.fPaint->getImageFilter()); if (disallowAtlasing) { continue; } SkRect layerRect; initialMat.mapRect(&layerRect, info.fBounds); if (!layerRect.intersect(query)) { continue; } const SkIRect dstIR = layerRect.roundOut(); SkIRect srcIR; if (!compute_source_rect(info, initialMat, dstIR, &srcIR) || !GrLayerCache::PlausiblyAtlasable(srcIR.width(), srcIR.height())) { continue; } prepare_for_hoisting(layerCache, topLevelPicture, initialMat, info, srcIR, dstIR, atlased, recycled, true, 0); } }
static SkBitmap unpremultiply_bitmap(const SkBitmap& bitmap, const SkIRect& srcRect) { SkBitmap outBitmap; outBitmap.allocPixels(bitmap.info().makeWH(srcRect.width(), srcRect.height())); int dstRow = 0; SkAutoLockPixels outBitmapPixelLock(outBitmap); SkAutoLockPixels bitmapPixelLock(bitmap); switch (bitmap.colorType()) { case kARGB_4444_SkColorType: { for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { uint16_t* dst = outBitmap.getAddr16(0, dstRow); uint16_t* src = bitmap.getAddr16(0, y); for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { uint8_t a = SkGetPackedA4444(src[x]); // It is necessary to average the color component of // transparent pixels with their surrounding neighbors // since the PDF renderer may separately re-sample the // alpha and color channels when the image is not // displayed at its native resolution. Since an alpha of // zero gives no information about the color component, // the pathological case is a white image with sharp // transparency bounds - the color channel goes to black, // and the should-be-transparent pixels are rendered // as grey because of the separate soft mask and color // resizing. if (a == (SK_AlphaTRANSPARENT & 0x0F)) { *dst = get_argb4444_neighbor_avg_color(bitmap, x, y); } else { *dst = remove_alpha_argb4444(src[x]); } dst++; } dstRow++; } break; } case kN32_SkColorType: { for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { uint32_t* dst = outBitmap.getAddr32(0, dstRow); uint32_t* src = bitmap.getAddr32(0, y); for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { uint8_t a = SkGetPackedA32(src[x]); if (a == SK_AlphaTRANSPARENT) { *dst = get_argb8888_neighbor_avg_color(bitmap, x, y); } else { *dst = remove_alpha_argb8888(src[x]); } dst++; } dstRow++; } break; } default: SkASSERT(false); } outBitmap.setImmutable(); return outBitmap; }
static SkIRect webRectToSkIRect(const WebRect& webRect) { SkIRect irect; irect.set(webRect.x, webRect.y, webRect.x + webRect.width - 1, webRect.y + webRect.height - 1); return irect; }
static void draw_nine_clipped(const SkMask& mask, const SkIRect& outerR, const SkIPoint& center, bool fillCenter, const SkIRect& clipR, SkBlitter* blitter) { int cx = center.x(); int cy = center.y(); SkMask m; // top-left m.fBounds = mask.fBounds; m.fBounds.fRight = cx; m.fBounds.fBottom = cy; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.left(), outerR.top()); blitClippedMask(blitter, m, m.fBounds, clipR); } // top-right m.fBounds = mask.fBounds; m.fBounds.fLeft = cx + 1; m.fBounds.fBottom = cy; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.top()); blitClippedMask(blitter, m, m.fBounds, clipR); } // bottom-left m.fBounds = mask.fBounds; m.fBounds.fRight = cx; m.fBounds.fTop = cy + 1; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.left(), outerR.bottom() - m.fBounds.height()); blitClippedMask(blitter, m, m.fBounds, clipR); } // bottom-right m.fBounds = mask.fBounds; m.fBounds.fLeft = cx + 1; m.fBounds.fTop = cy + 1; if (m.fBounds.width() > 0 && m.fBounds.height() > 0) { extractMaskSubset(mask, &m); m.fBounds.offsetTo(outerR.right() - m.fBounds.width(), outerR.bottom() - m.fBounds.height()); blitClippedMask(blitter, m, m.fBounds, clipR); } SkIRect innerR; innerR.set(outerR.left() + cx - mask.fBounds.left(), outerR.top() + cy - mask.fBounds.top(), outerR.right() + (cx + 1 - mask.fBounds.right()), outerR.bottom() + (cy + 1 - mask.fBounds.bottom())); if (fillCenter) { blitClippedRect(blitter, innerR, clipR); } const int innerW = innerR.width(); size_t storageSize = (innerW + 1) * (sizeof(int16_t) + sizeof(uint8_t)); SkAutoSMalloc<4*1024> storage(storageSize); int16_t* runs = (int16_t*)storage.get(); uint8_t* alpha = (uint8_t*)(runs + innerW + 1); SkIRect r; // top r.set(innerR.left(), outerR.top(), innerR.right(), innerR.top()); if (r.intersect(clipR)) { int startY = SkMax32(0, r.top() - outerR.top()); int stopY = startY + r.height(); int width = r.width(); for (int y = startY; y < stopY; ++y) { runs[0] = width; runs[width] = 0; alpha[0] = *mask.getAddr8(cx, mask.fBounds.top() + y); blitter->blitAntiH(r.left(), outerR.top() + y, alpha, runs); } } // bottom r.set(innerR.left(), innerR.bottom(), innerR.right(), outerR.bottom()); if (r.intersect(clipR)) { int startY = outerR.bottom() - r.bottom(); int stopY = startY + r.height(); int width = r.width(); for (int y = startY; y < stopY; ++y) { runs[0] = width; runs[width] = 0; alpha[0] = *mask.getAddr8(cx, mask.fBounds.bottom() - y - 1); blitter->blitAntiH(r.left(), outerR.bottom() - y - 1, alpha, runs); } } // left r.set(outerR.left(), innerR.top(), innerR.left(), innerR.bottom()); if (r.intersect(clipR)) { int startX = r.left() - outerR.left(); int stopX = startX + r.width(); int height = r.height(); for (int x = startX; x < stopX; ++x) { blitter->blitV(outerR.left() + x, r.top(), height, *mask.getAddr8(mask.fBounds.left() + x, mask.fBounds.top() + cy)); } } // right r.set(innerR.right(), innerR.top(), outerR.right(), innerR.bottom()); if (r.intersect(clipR)) { int startX = outerR.right() - r.right(); int stopX = startX + r.width(); int height = r.height(); for (int x = startX; x < stopX; ++x) { blitter->blitV(outerR.right() - x - 1, r.top(), height, *mask.getAddr8(mask.fBounds.right() - x - 1, mask.fBounds.top() + cy)); } } }
IntRect::IntRect(const SkIRect& r) : m_location(r.fLeft, r.fTop) , m_size(r.width(), r.height()) { }
void GrVkGpuCommandBuffer::onClear(GrRenderTarget* target, const GrFixedClip& clip, GrColor color) { // parent class should never let us get here with no RT SkASSERT(target); SkASSERT(!clip.hasWindowRectangles()); VkClearColorValue vkColor; GrColorToRGBAFloat(color, vkColor.float32); GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(target); if (fIsEmpty && !clip.scissorEnabled()) { // We will change the render pass to do a clear load instead GrVkRenderPass::LoadStoreOps vkColorOps(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE); GrVkRenderPass::LoadStoreOps vkStencilOps(VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE); const GrVkRenderPass* oldRP = fRenderPass; const GrVkResourceProvider::CompatibleRPHandle& rpHandle = vkRT->compatibleRenderPassHandle(); if (rpHandle.isValid()) { fRenderPass = fGpu->resourceProvider().findRenderPass(rpHandle, vkColorOps, vkStencilOps); } else { fRenderPass = fGpu->resourceProvider().findRenderPass(*vkRT, vkColorOps, vkStencilOps); } SkASSERT(fRenderPass->isCompatible(*oldRP)); oldRP->unref(fGpu); GrColorToRGBAFloat(color, fColorClearValue.color.float32); fStartsWithClear = true; return; } // We always do a sub rect clear with clearAttachments since we are inside a render pass VkClearRect clearRect; // Flip rect if necessary SkIRect vkRect; if (!clip.scissorEnabled()) { vkRect.setXYWH(0, 0, vkRT->width(), vkRT->height()); } else if (kBottomLeft_GrSurfaceOrigin != vkRT->origin()) { vkRect = clip.scissorRect(); } else { const SkIRect& scissor = clip.scissorRect(); vkRect.setLTRB(scissor.fLeft, vkRT->height() - scissor.fBottom, scissor.fRight, vkRT->height() - scissor.fTop); } clearRect.rect.offset = { vkRect.fLeft, vkRect.fTop }; clearRect.rect.extent = { (uint32_t)vkRect.width(), (uint32_t)vkRect.height() }; clearRect.baseArrayLayer = 0; clearRect.layerCount = 1; uint32_t colorIndex; SkAssertResult(fRenderPass->colorAttachmentIndex(&colorIndex)); VkClearAttachment attachment; attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; attachment.colorAttachment = colorIndex; attachment.clearValue.color = vkColor; fCommandBuffer->clearAttachments(fGpu, 1, &attachment, 1, &clearRect); fIsEmpty = false; return; }
SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) { fState.get()->fImage.lockPixels(); // The image shader pattern cell will be drawn into a separate device // in pattern cell space (no scaling on the bitmap, though there may be // translations so that all content is in the device, coordinates > 0). // Map clip bounds to shader space to ensure the device is large enough // to handle fake clamping. SkMatrix finalMatrix = fState.get()->fCanvasTransform; finalMatrix.preConcat(fState.get()->fShaderTransform); SkRect deviceBounds; deviceBounds.set(fState.get()->fBBox); if (!inverseTransformBBox(finalMatrix, &deviceBounds)) { return; } const SkBitmap* image = &fState.get()->fImage; SkRect bitmapBounds; image->getBounds(&bitmapBounds); // For tiling modes, the bounds should be extended to include the bitmap, // otherwise the bitmap gets clipped out and the shader is empty and awful. // For clamp modes, we're only interested in the clip region, whether // or not the main bitmap is in it. SkShader::TileMode tileModes[2]; tileModes[0] = fState.get()->fImageTileModes[0]; tileModes[1] = fState.get()->fImageTileModes[1]; if (tileModes[0] != SkShader::kClamp_TileMode || tileModes[1] != SkShader::kClamp_TileMode) { deviceBounds.join(bitmapBounds); } SkMatrix unflip; unflip.setTranslate(0, SkScalarRoundToScalar(deviceBounds.height())); unflip.preScale(SK_Scalar1, -SK_Scalar1); SkISize size = SkISize::Make(SkScalarRound(deviceBounds.width()), SkScalarRound(deviceBounds.height())); SkPDFDevice pattern(size, size, unflip); SkCanvas canvas(&pattern); SkRect patternBBox; image->getBounds(&patternBBox); // Translate the canvas so that the bitmap origin is at (0, 0). canvas.translate(-deviceBounds.left(), -deviceBounds.top()); patternBBox.offset(-deviceBounds.left(), -deviceBounds.top()); // Undo the translation in the final matrix finalMatrix.preTranslate(deviceBounds.left(), deviceBounds.top()); // If the bitmap is out of bounds (i.e. clamp mode where we only see the // stretched sides), canvas will clip this out and the extraneous data // won't be saved to the PDF. canvas.drawBitmap(*image, 0, 0); SkScalar width = SkIntToScalar(image->width()); SkScalar height = SkIntToScalar(image->height()); // Tiling is implied. First we handle mirroring. if (tileModes[0] == SkShader::kMirror_TileMode) { SkMatrix xMirror; xMirror.setScale(-1, 1); xMirror.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(*image, xMirror); patternBBox.fRight += width; } if (tileModes[1] == SkShader::kMirror_TileMode) { SkMatrix yMirror; yMirror.setScale(SK_Scalar1, -SK_Scalar1); yMirror.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(*image, yMirror); patternBBox.fBottom += height; } if (tileModes[0] == SkShader::kMirror_TileMode && tileModes[1] == SkShader::kMirror_TileMode) { SkMatrix mirror; mirror.setScale(-1, -1); mirror.postTranslate(2 * width, 2 * height); canvas.drawBitmapMatrix(*image, mirror); } // Then handle Clamping, which requires expanding the pattern canvas to // cover the entire surfaceBBox. // If both x and y are in clamp mode, we start by filling in the corners. // (Which are just a rectangles of the corner colors.) if (tileModes[0] == SkShader::kClamp_TileMode && tileModes[1] == SkShader::kClamp_TileMode) { SkPaint paint; SkRect rect; rect = SkRect::MakeLTRB(deviceBounds.left(), deviceBounds.top(), 0, 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, deviceBounds.top(), deviceBounds.right(), 0); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, 0)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(width, height, deviceBounds.right(), deviceBounds.bottom()); if (!rect.isEmpty()) { paint.setColor(image->getColor(image->width() - 1, image->height() - 1)); canvas.drawRect(rect, paint); } rect = SkRect::MakeLTRB(deviceBounds.left(), height, 0, deviceBounds.bottom()); if (!rect.isEmpty()) { paint.setColor(image->getColor(0, image->height() - 1)); canvas.drawRect(rect, paint); } } // Then expand the left, right, top, then bottom. if (tileModes[0] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, image->height()); if (deviceBounds.left() < 0) { SkBitmap left; SkAssertResult(image->extractSubset(&left, subset)); SkMatrix leftMatrix; leftMatrix.setScale(-deviceBounds.left(), 1); leftMatrix.postTranslate(deviceBounds.left(), 0); canvas.drawBitmapMatrix(left, leftMatrix); if (tileModes[1] == SkShader::kMirror_TileMode) { leftMatrix.postScale(SK_Scalar1, -SK_Scalar1); leftMatrix.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(left, leftMatrix); } patternBBox.fLeft = 0; } if (deviceBounds.right() > width) { SkBitmap right; subset.offset(image->width() - 1, 0); SkAssertResult(image->extractSubset(&right, subset)); SkMatrix rightMatrix; rightMatrix.setScale(deviceBounds.right() - width, 1); rightMatrix.postTranslate(width, 0); canvas.drawBitmapMatrix(right, rightMatrix); if (tileModes[1] == SkShader::kMirror_TileMode) { rightMatrix.postScale(SK_Scalar1, -SK_Scalar1); rightMatrix.postTranslate(0, 2 * height); canvas.drawBitmapMatrix(right, rightMatrix); } patternBBox.fRight = deviceBounds.width(); } } if (tileModes[1] == SkShader::kClamp_TileMode) { SkIRect subset = SkIRect::MakeXYWH(0, 0, image->width(), 1); if (deviceBounds.top() < 0) { SkBitmap top; SkAssertResult(image->extractSubset(&top, subset)); SkMatrix topMatrix; topMatrix.setScale(SK_Scalar1, -deviceBounds.top()); topMatrix.postTranslate(0, deviceBounds.top()); canvas.drawBitmapMatrix(top, topMatrix); if (tileModes[0] == SkShader::kMirror_TileMode) { topMatrix.postScale(-1, 1); topMatrix.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(top, topMatrix); } patternBBox.fTop = 0; } if (deviceBounds.bottom() > height) { SkBitmap bottom; subset.offset(0, image->height() - 1); SkAssertResult(image->extractSubset(&bottom, subset)); SkMatrix bottomMatrix; bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height); bottomMatrix.postTranslate(0, height); canvas.drawBitmapMatrix(bottom, bottomMatrix); if (tileModes[0] == SkShader::kMirror_TileMode) { bottomMatrix.postScale(-1, 1); bottomMatrix.postTranslate(2 * width, 0); canvas.drawBitmapMatrix(bottom, bottomMatrix); } patternBBox.fBottom = deviceBounds.height(); } } // Put the canvas into the pattern stream (fContent). SkAutoTUnref<SkStream> content(pattern.content()); setData(content.get()); SkPDFResourceDict* resourceDict = pattern.getResourceDict(); resourceDict->getReferencedResources(fResources, &fResources, false); populate_tiling_pattern_dict(this, patternBBox, pattern.getResourceDict(), finalMatrix); fState.get()->fImage.unlockPixels(); }
bool GrDrawTarget::setupDstReadIfNecessary(const GrPipelineBuilder& pipelineBuilder, const GrProcOptInfo& colorPOI, const GrProcOptInfo& coveragePOI, GrXferProcessor::DstTexture* dstTexture, const SkRect* drawBounds) { if (!pipelineBuilder.willXPNeedDstTexture(*this->caps(), colorPOI, coveragePOI)) { return true; } GrRenderTarget* rt = pipelineBuilder.getRenderTarget(); if (this->caps()->textureBarrierSupport()) { if (GrTexture* rtTex = rt->asTexture()) { // The render target is a texture, so we can read from it directly in the shader. The XP // will be responsible to detect this situation and request a texture barrier. dstTexture->setTexture(rtTex); dstTexture->setOffset(0, 0); return true; } } SkIRect copyRect; pipelineBuilder.clip().getConservativeBounds(rt, ©Rect); if (drawBounds) { SkIRect drawIBounds; drawBounds->roundOut(&drawIBounds); if (!copyRect.intersect(drawIBounds)) { #ifdef SK_DEBUG GrCapsDebugf(fCaps, "Missed an early reject. " "Bailing on draw from setupDstReadIfNecessary.\n"); #endif return false; } } else { #ifdef SK_DEBUG //SkDebugf("No dev bounds when dst copy is made.\n"); #endif } // MSAA consideration: When there is support for reading MSAA samples in the shader we could // have per-sample dst values by making the copy multisampled. GrSurfaceDesc desc; if (!this->getGpu()->initCopySurfaceDstDesc(rt, &desc)) { desc.fOrigin = kDefault_GrSurfaceOrigin; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fConfig = rt->config(); } desc.fWidth = copyRect.width(); desc.fHeight = copyRect.height(); SkAutoTUnref<GrTexture> copy( fResourceProvider->refScratchTexture(desc, GrTextureProvider::kApprox_ScratchTexMatch)); if (!copy) { SkDebugf("Failed to create temporary copy of destination texture.\n"); return false; } SkIPoint dstPoint = {0, 0}; this->copySurface(copy, rt, copyRect, dstPoint); dstTexture->setTexture(copy); dstTexture->setOffset(copyRect.fLeft, copyRect.fTop); return true; }
bool GrSWMaskHelper::init(const SkIRect& resultBounds, const SkMatrix* matrix, bool allowCompression) { if (matrix) { fMatrix = *matrix; } else { fMatrix.setIdentity(); } // Now translate so the bound's UL corner is at the origin fMatrix.postTranslate(-resultBounds.fLeft * SK_Scalar1, -resultBounds.fTop * SK_Scalar1); SkIRect bounds = SkIRect::MakeWH(resultBounds.width(), resultBounds.height()); if (allowCompression && fContext->caps()->drawPathMasksToCompressedTexturesSupport() && choose_compressed_fmt(fContext->caps(), &fCompressedFormat)) { fCompressionMode = kCompress_CompressionMode; } // Make sure that the width is a multiple of the desired block dimensions // to allow for specialized SIMD instructions that compress multiple blocks at a time. int cmpWidth = bounds.fRight; int cmpHeight = bounds.fBottom; if (kCompress_CompressionMode == fCompressionMode) { int dimX, dimY; SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY); cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX); cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY); // Can we create a blitter? if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) { int cmpSz = SkTextureCompressor::GetCompressedDataSize( fCompressedFormat, cmpWidth, cmpHeight); SkASSERT(cmpSz > 0); SkASSERT(nullptr == fCompressedBuffer.get()); fCompressedBuffer.reset(cmpSz); fCompressionMode = kBlitter_CompressionMode; } } sk_bzero(&fDraw, sizeof(fDraw)); // If we don't have a custom blitter, then we either need a bitmap to compress // from or a bitmap that we're going to use as a texture. In any case, we should // allocate the pixels for a bitmap const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight); if (kBlitter_CompressionMode != fCompressionMode) { if (!fPixels.tryAlloc(bmImageInfo)) { return false; } fPixels.erase(0); } else { // Otherwise, we just need to remember how big the buffer is... fPixels.reset(bmImageInfo); } fDraw.fDst = fPixels; fRasterClip.setRect(bounds); fDraw.fRC = &fRasterClip; fDraw.fClip = &fRasterClip.bwRgn(); fDraw.fMatrix = &fMatrix; return true; }
SkPDFShader::State::State(const SkShader& shader, const SkMatrix& canvasTransform, const SkIRect& bbox, SkScalar rasterScale) : fCanvasTransform(canvasTransform), fBBox(bbox), fPixelGeneration(0) { fInfo.fColorCount = 0; fInfo.fColors = NULL; fInfo.fColorOffsets = NULL; fShaderTransform = shader.getLocalMatrix(); fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode; fType = shader.asAGradient(&fInfo); if (fType == SkShader::kNone_GradientType) { SkMatrix matrix; if (shader.isABitmap(&fImage, &matrix, fImageTileModes)) { SkASSERT(matrix.isIdentity()); } else { // Generic fallback for unsupported shaders: // * allocate a bbox-sized bitmap // * shade the whole area // * use the result as a bitmap shader // bbox is in device space. While that's exactly what we want for sizing our bitmap, // we need to map it into shader space for adjustments (to match // SkPDFImageShader::Create's behavior). SkRect shaderRect = SkRect::Make(bbox); if (!inverse_transform_bbox(canvasTransform, &shaderRect)) { fImage.reset(); return; } // Clamp the bitmap size to about 1M pixels static const SkScalar kMaxBitmapArea = 1024 * 1024; SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height(); if (bitmapArea > kMaxBitmapArea) { rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea); } SkISize size = SkISize::Make(SkScalarRoundToInt(rasterScale * bbox.width()), SkScalarRoundToInt(rasterScale * bbox.height())); SkSize scale = SkSize::Make(SkIntToScalar(size.width()) / shaderRect.width(), SkIntToScalar(size.height()) / shaderRect.height()); fImage.allocN32Pixels(size.width(), size.height()); fImage.eraseColor(SK_ColorTRANSPARENT); SkPaint p; p.setShader(const_cast<SkShader*>(&shader)); SkCanvas canvas(fImage); canvas.scale(scale.width(), scale.height()); canvas.translate(-shaderRect.x(), -shaderRect.y()); canvas.drawPaint(p); fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y()); fShaderTransform.preScale(1 / scale.width(), 1 / scale.height()); } fPixelGeneration = fImage.getGenerationID(); } else { AllocateGradientInfoStorage(); shader.asAGradient(&fInfo); } }
void build_base_rgn(SkRegion* rgn) { rgn->setRect(fBase); SkIRect r = fBase; r.offset(75, 20); rgn->op(r, SkRegion::kUnion_Op); }
void TiledPage::prepare(bool goingDown, bool goingLeft, const SkIRect& tileBounds, PrepareBounds bounds) { if (!m_glWebViewState) return; TilesManager::instance()->gatherTextures(); m_scrollingDown = goingDown; int firstTileX = tileBounds.fLeft; int firstTileY = tileBounds.fTop; int nbTilesWidth = tileBounds.width(); int nbTilesHeight = tileBounds.height(); int lastTileX = tileBounds.fRight - 1; int lastTileY = tileBounds.fBottom - 1; // Expand number of tiles to allow tiles outside of viewport to be prepared for // smoother scrolling. int nTilesToPrepare = nbTilesWidth * nbTilesHeight; int nMaxTilesPerPage = m_baseTileSize / 2; if (bounds == ExpandedBounds) { // prepare tiles outside of the visible bounds int expandX = m_glWebViewState->expandedTileBoundsX(); int expandY = m_glWebViewState->expandedTileBoundsY(); firstTileX -= expandX; lastTileX += expandX; nbTilesWidth += expandX * 2; firstTileY -= expandY; lastTileY += expandY; nbTilesHeight += expandY * 2; } // crop the prepared region to the contents of the base layer float maxWidthTiles = m_glWebViewState->baseContentWidth() * m_scale / TilesManager::tileWidth(); float maxHeightTiles = m_glWebViewState->baseContentHeight() * m_scale / TilesManager::tileHeight(); firstTileX = std::max(0, firstTileX); firstTileY = std::max(0, firstTileY); lastTileX = std::min(lastTileX, static_cast<int>(ceilf(maxWidthTiles)) - 1); lastTileY = std::min(lastTileY, static_cast<int>(ceilf(maxHeightTiles)) - 1); m_expandedTileBounds.fLeft = firstTileX; m_expandedTileBounds.fTop = firstTileY; m_expandedTileBounds.fRight = lastTileX; m_expandedTileBounds.fBottom = lastTileY; // check against corrupted scale values giving bad height/width (use float to avoid overflow) float numTiles = static_cast<float>(nbTilesHeight) * static_cast<float>(nbTilesWidth); if (numTiles > TilesManager::getMaxTextureAllocation() || nbTilesHeight < 1 || nbTilesWidth < 1) { XLOGC("ERROR: We don't have enough tiles for this page!" " nbTilesHeight %d nbTilesWidth %d", nbTilesHeight, nbTilesWidth); return; } for (int i = 0; i < nbTilesHeight; i++) prepareRow(goingLeft, nbTilesWidth, firstTileX, firstTileY + i, tileBounds); m_prepare = true; }
bool SkBicubicImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const SkMatrix& matrix, SkBitmap* result, SkIPoint* loc) { SkBitmap src = source; if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) { return false; } if (src.config() != SkBitmap::kARGB_8888_Config) { return false; } SkAutoLockPixels alp(src); if (!src.getPixels()) { return false; } SkRect dstRect = SkRect::MakeWH(SkScalarMul(SkIntToScalar(src.width()), fScale.fWidth), SkScalarMul(SkIntToScalar(src.height()), fScale.fHeight)); SkIRect dstIRect; dstRect.roundOut(&dstIRect); if (dstIRect.isEmpty()) { return false; } result->setConfig(src.config(), dstIRect.width(), dstIRect.height()); result->allocPixels(); if (!result->getPixels()) { return false; } SkRect srcRect; src.getBounds(&srcRect); SkMatrix inverse; inverse.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit); inverse.postTranslate(SkFloatToScalar(-0.5f), SkFloatToScalar(-0.5f)); for (int y = dstIRect.fTop; y < dstIRect.fBottom; ++y) { SkPMColor* dptr = result->getAddr32(dstIRect.fLeft, y); for (int x = dstIRect.fLeft; x < dstIRect.fRight; ++x) { SkPoint srcPt, dstPt = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); inverse.mapPoints(&srcPt, &dstPt, 1); SkScalar fractx = srcPt.fX - SkScalarFloorToScalar(srcPt.fX); SkScalar fracty = srcPt.fY - SkScalarFloorToScalar(srcPt.fY); int sx = SkScalarFloorToInt(srcPt.fX); int sy = SkScalarFloorToInt(srcPt.fY); int x0 = SkClampMax(sx - 1, src.width() - 1); int x1 = SkClampMax(sx , src.width() - 1); int x2 = SkClampMax(sx + 1, src.width() - 1); int x3 = SkClampMax(sx + 2, src.width() - 1); int y0 = SkClampMax(sy - 1, src.height() - 1); int y1 = SkClampMax(sy , src.height() - 1); int y2 = SkClampMax(sy + 1, src.height() - 1); int y3 = SkClampMax(sy + 2, src.height() - 1); SkPMColor s00 = *src.getAddr32(x0, y0); SkPMColor s10 = *src.getAddr32(x1, y0); SkPMColor s20 = *src.getAddr32(x2, y0); SkPMColor s30 = *src.getAddr32(x3, y0); SkPMColor s0 = cubicBlend(fCoefficients, fractx, s00, s10, s20, s30); SkPMColor s01 = *src.getAddr32(x0, y1); SkPMColor s11 = *src.getAddr32(x1, y1); SkPMColor s21 = *src.getAddr32(x2, y1); SkPMColor s31 = *src.getAddr32(x3, y1); SkPMColor s1 = cubicBlend(fCoefficients, fractx, s01, s11, s21, s31); SkPMColor s02 = *src.getAddr32(x0, y2); SkPMColor s12 = *src.getAddr32(x1, y2); SkPMColor s22 = *src.getAddr32(x2, y2); SkPMColor s32 = *src.getAddr32(x3, y2); SkPMColor s2 = cubicBlend(fCoefficients, fractx, s02, s12, s22, s32); SkPMColor s03 = *src.getAddr32(x0, y3); SkPMColor s13 = *src.getAddr32(x1, y3); SkPMColor s23 = *src.getAddr32(x2, y3); SkPMColor s33 = *src.getAddr32(x3, y3); SkPMColor s3 = cubicBlend(fCoefficients, fractx, s03, s13, s23, s33); *dptr++ = cubicBlend(fCoefficients, fracty, s0, s1, s2, s3); } } return true; }
bool CopyTilesRenderer::render(SkBitmap** out) { int i = 0; bool success = true; SkBitmap dst; for (int x = 0; x < this->getViewWidth(); x += fLargeTileWidth) { for (int y = 0; y < this->getViewHeight(); y += fLargeTileHeight) { SkAutoCanvasRestore autoRestore(fCanvas, true); // Translate so that we draw the correct portion of the picture. // Perform a postTranslate so that the scaleFactor does not interfere with the // positioning. SkMatrix mat(fCanvas->getTotalMatrix()); mat.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y)); fCanvas->setMatrix(mat); // Draw the picture if (fUseMultiPictureDraw) { SkMultiPictureDraw mpd; mpd.add(fCanvas, fPicture); mpd.draw(); } else { fCanvas->drawPicture(fPicture); } // Now extract the picture into tiles SkBitmap baseBitmap; fCanvas->readPixels(SkIRect::MakeSize(fCanvas->getBaseLayerSize()), &baseBitmap); SkIRect subset; for (int tileY = 0; tileY < fLargeTileHeight; tileY += this->getTileHeight()) { for (int tileX = 0; tileX < fLargeTileWidth; tileX += this->getTileWidth()) { subset.set(tileX, tileY, tileX + this->getTileWidth(), tileY + this->getTileHeight()); SkDEBUGCODE(bool extracted =) baseBitmap.extractSubset(&dst, subset); SkASSERT(extracted); if (!fWritePath.isEmpty()) { // Similar to write() in PictureRenderer.cpp, but just encodes // a bitmap directly. // TODO: Share more common code with write() to do this, to properly // write out the JSON summary, etc. SkString pathWithNumber = SkOSPath::Join(fWritePath.c_str(), fInputFilename.c_str()); pathWithNumber.remove(pathWithNumber.size() - 4, 4); pathWithNumber.appendf("%i.png", i++); SkBitmap copy; #if SK_SUPPORT_GPU if (isUsingGpuDevice()) { dst.pixelRef()->readPixels(©, &subset); } else { #endif dst.copyTo(©); #if SK_SUPPORT_GPU } #endif success &= SkImageEncoder::EncodeFile(pathWithNumber.c_str(), copy, SkImageEncoder::kPNG_Type, 100); } } } } } return success; }
static void blitClippedRect(SkBlitter* blitter, const SkIRect& rect, const SkIRect& clipR) { SkIRect r; if (r.intersect(rect, clipR)) { blitter->blitRect(r.left(), r.top(), r.width(), r.height()); } }
void draw(SkCanvas* canvas) { SkIRect result; bool intersected = result.intersect({ 10, 40, 50, 80 }, { 30, 60, 70, 90 }); SkDebugf("%s intersection: %d, %d, %d, %d\n", intersected ? "" : "no ", result.left(), result.top(), result.right(), result.bottom()); }
void draw(SkCanvas* canvas) { SkIRect leftRect = { 10, 40, 50, 80 }; SkDebugf("%s intersection: ", leftRect.intersect(30, 60, 70, 90) ? "" : "no "); SkDebugf("%d, %d, %d, %d\n", leftRect.left(), leftRect.top(), leftRect.right(), leftRect.bottom()); }
sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* source, const Context& ctx, SkIPoint* offset) const { SkIPoint inputOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); if (!input) { return nullptr; } const SkIRect inputBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(), input->width(), input->height()); SkIRect bounds; if (!this->applyCropRect(ctx, inputBounds, &bounds)) { return nullptr; } SkScalar invInset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; SkScalar invXZoom = fSrcRect.width() / bounds.width(); SkScalar invYZoom = fSrcRect.height() / bounds.height(); #if SK_SUPPORT_GPU if (source->isTextureBacked()) { GrContext* context = source->getContext(); sk_sp<GrTexture> inputTexture(input->asTextureRef(context)); SkASSERT(inputTexture); offset->fX = bounds.left(); offset->fY = bounds.top(); bounds.offset(-inputOffset); SkScalar yOffset = inputTexture->origin() == kTopLeft_GrSurfaceOrigin ? fSrcRect.y() : inputTexture->height() - fSrcRect.height() * inputTexture->height() / bounds.height() - fSrcRect.y(); int boundsY = inputTexture->origin() == kTopLeft_GrSurfaceOrigin ? bounds.y() : inputTexture->height() - bounds.height(); SkRect effectBounds = SkRect::MakeXYWH( SkIntToScalar(bounds.x()) / inputTexture->width(), SkIntToScalar(boundsY) / inputTexture->height(), SkIntToScalar(inputTexture->width()) / bounds.width(), SkIntToScalar(inputTexture->height()) / bounds.height()); // SRGBTODO: Handle sRGB here sk_sp<GrFragmentProcessor> fp(GrMagnifierEffect::Create( inputTexture.get(), effectBounds, fSrcRect.x() / inputTexture->width(), yOffset / inputTexture->height(), invXZoom, invYZoom, bounds.width() * invInset, bounds.height() * invInset)); if (!fp) { return nullptr; } return DrawWithFP(context, std::move(fp), bounds); } #endif SkBitmap inputBM; if (!input->getROPixels(&inputBM)) { return nullptr; } if ((inputBM.colorType() != kN32_SkColorType) || (fSrcRect.width() >= inputBM.width()) || (fSrcRect.height() >= inputBM.height())) { return nullptr; } SkAutoLockPixels alp(inputBM); SkASSERT(inputBM.getPixels()); if (!inputBM.getPixels() || inputBM.width() <= 0 || inputBM.height() <= 0) { return nullptr; } const SkImageInfo info = SkImageInfo::MakeN32Premul(bounds.width(), bounds.height()); SkBitmap dst; if (!dst.tryAllocPixels(info)) { return nullptr; } SkAutoLockPixels dstLock(dst); SkColor* dptr = dst.getAddr32(0, 0); int dstWidth = dst.width(), dstHeight = dst.height(); for (int y = 0; y < dstHeight; ++y) { for (int x = 0; x < dstWidth; ++x) { SkScalar x_dist = SkMin32(x, dstWidth - x - 1) * invInset; SkScalar y_dist = SkMin32(y, dstHeight - y - 1) * invInset; SkScalar weight = 0; static const SkScalar kScalar2 = SkScalar(2); // To create a smooth curve at the corners, we need to work on // a square twice the size of the inset. if (x_dist < kScalar2 && y_dist < kScalar2) { x_dist = kScalar2 - x_dist; y_dist = kScalar2 - y_dist; SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + SkScalarSquare(y_dist)); dist = SkMaxScalar(kScalar2 - dist, 0); weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); } else { SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), SkScalarSquare(y_dist)); weight = SkMinScalar(sqDist, SK_Scalar1); } SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * invXZoom)) + (SK_Scalar1 - weight) * x; SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * invYZoom)) + (SK_Scalar1 - weight) * y; int x_val = SkTPin(bounds.x() + SkScalarFloorToInt(x_interp), 0, inputBM.width() - 1); int y_val = SkTPin(bounds.y() + SkScalarFloorToInt(y_interp), 0, inputBM.height() - 1); *dptr = *inputBM.getAddr32(x_val, y_val); dptr++; } } offset->fX = bounds.left(); offset->fY = bounds.top(); return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), dst); }
/** * Clip restriction logic exists in the canvas itself, and in various kinds of devices. * * This test explicitly tries to exercise that variety: * - picture : empty device but exercises canvas itself * - pdf : uses SkClipStack in its device (as does SVG and GPU) * - raster : uses SkRasterClip in its device */ DEF_TEST(canvas_clip_restriction, reporter) { multi_canvas_driver(gBaseRestrictedR.width(), gBaseRestrictedR.height(), [reporter](SkCanvas* canvas) { test_restriction(reporter, canvas); }); }
//////////////////////////////////////////////////////////////////////////////// // sort out what kind of clip mask needs to be created: alpha, stencil, // scissor, or entirely software bool GrClipMaskManager::setupClipping(GrPipelineBuilder* pipelineBuilder, GrPipelineBuilder::AutoRestoreFragmentProcessors* arfp, GrPipelineBuilder::AutoRestoreStencil* ars, GrScissorState* scissorState, const SkRect* devBounds) { fCurrClipMaskType = kNone_ClipMaskType; if (kRespectClip_StencilClipMode == fClipMode) { fClipMode = kIgnoreClip_StencilClipMode; } GrReducedClip::ElementList elements(16); int32_t genID = 0; GrReducedClip::InitialState initialState = GrReducedClip::kAllIn_InitialState; SkIRect clipSpaceIBounds; bool requiresAA = false; GrRenderTarget* rt = pipelineBuilder->getRenderTarget(); // GrDrawTarget should have filtered this for us SkASSERT(rt); SkIRect clipSpaceRTIBounds = SkIRect::MakeWH(rt->width(), rt->height()); const GrClip& clip = pipelineBuilder->clip(); if (clip.isWideOpen(clipSpaceRTIBounds)) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // The clip mask manager always draws with a single IRect so we special case that logic here // Image filters just use a rect, so we also special case that logic switch (clip.clipType()) { case GrClip::kWideOpen_ClipType: SkFAIL("Should have caught this with clip.isWideOpen()"); return true; case GrClip::kIRect_ClipType: { SkIRect scissor = clip.irect(); if (scissor.intersect(clipSpaceRTIBounds)) { scissorState->set(scissor); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } return false; } case GrClip::kClipStack_ClipType: { clipSpaceRTIBounds.offset(clip.origin()); GrReducedClip::ReduceClipStack(*clip.clipStack(), clipSpaceRTIBounds, &elements, &genID, &initialState, &clipSpaceIBounds, &requiresAA); if (elements.isEmpty()) { if (GrReducedClip::kAllIn_InitialState == initialState) { if (clipSpaceIBounds == clipSpaceRTIBounds) { this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } } else { return false; } } } break; } // An element count of 4 was chosen because of the common pattern in Blink of: // isect RR // diff RR // isect convex_poly // isect convex_poly // when drawing rounded div borders. This could probably be tuned based on a // configuration's relative costs of switching RTs to generate a mask vs // longer shaders. if (elements.count() <= 4) { SkVector clipToRTOffset = { SkIntToScalar(-clip.origin().fX), SkIntToScalar(-clip.origin().fY) }; if (elements.isEmpty() || (requiresAA && this->installClipEffects(pipelineBuilder, arfp, elements, clipToRTOffset, devBounds))) { SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(-clip.origin()); if (NULL == devBounds || !SkRect::Make(scissorSpaceIBounds).contains(*devBounds)) { scissorState->set(scissorSpaceIBounds); } this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } } // If MSAA is enabled we can do everything in the stencil buffer. if (0 == rt->numSamples() && requiresAA) { GrTexture* result = NULL; // The top-left of the mask corresponds to the top-left corner of the bounds. SkVector clipToMaskOffset = { SkIntToScalar(-clipSpaceIBounds.fLeft), SkIntToScalar(-clipSpaceIBounds.fTop) }; if (this->useSWOnlyPath(pipelineBuilder, clipToMaskOffset, elements)) { // The clip geometry is complex enough that it will be more efficient to create it // entirely in software result = this->createSoftwareClipMask(genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds); } else { result = this->createAlphaClipMask(genID, initialState, elements, clipToMaskOffset, clipSpaceIBounds); } if (result) { arfp->set(pipelineBuilder); // The mask's top left coord should be pinned to the rounded-out top left corner of // clipSpace bounds. We determine the mask's position WRT to the render target here. SkIRect rtSpaceMaskBounds = clipSpaceIBounds; rtSpaceMaskBounds.offset(-clip.origin()); setup_drawstate_aaclip(pipelineBuilder, result, arfp, rtSpaceMaskBounds); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; } // if alpha clip mask creation fails fall through to the non-AA code paths } // Either a hard (stencil buffer) clip was explicitly requested or an anti-aliased clip couldn't // be created. In either case, free up the texture in the anti-aliased mask cache. // TODO: this may require more investigation. Ganesh performs a lot of utility draws (e.g., // clears, InOrderDrawBuffer playbacks) that hit the stencil buffer path. These may be // "incorrectly" clearing the AA cache. fAACache.reset(); // use the stencil clip if we can't represent the clip as a rectangle. SkIPoint clipSpaceToStencilSpaceOffset = -clip.origin(); this->createStencilClipMask(rt, genID, initialState, elements, clipSpaceIBounds, clipSpaceToStencilSpaceOffset); // This must occur after createStencilClipMask. That function may change the scissor. Also, it // only guarantees that the stencil mask is correct within the bounds it was passed, so we must // use both stencil and scissor test to the bounds for the final draw. SkIRect scissorSpaceIBounds(clipSpaceIBounds); scissorSpaceIBounds.offset(clipSpaceToStencilSpaceOffset); scissorState->set(scissorSpaceIBounds); this->setPipelineBuilderStencil(pipelineBuilder, ars); return true; }
static void toString(const SkIRect& r, SkString* str) { str->appendf("[%d,%d %d:%d]", r.fLeft, r.fTop, r.width(), r.height()); }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding, bool supportsSubsetDecoding, bool supports565 = true, bool supportsIncomplete = true) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(nullptr); bool isIncomplete = supportsIncomplete; if (isIncomplete) { size_t size = stream->getLength(); SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3))); codec.reset(SkCodec::NewFromData(data)); } else { codec.reset(SkCodec::NewFromStream(stream.detach())); } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // Test full image decodes with SkCodec SkMD5::Digest codecDigest; SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); SkBitmap bm; SkCodec::Result expectedResult = isIncomplete ? SkCodec::kIncompleteInput : SkCodec::kSuccess; test_codec(r, codec, bm, info, size, supports565, expectedResult, &codecDigest, nullptr); // Scanline decoding follows. // Need to call startScanlineDecode() first. REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) == 0); const SkCodec::Result startResult = codec->startScanlineDecode(info); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, startResult == SkCodec::kSuccess); for (int y = 0; y < info.height(); y++) { const int lines = codec->getScanlines(bm.getAddr(0, y), 1, 0); if (!isIncomplete) { REPORTER_ASSERT(r, 1 == lines); } } // verify that scanline decoding gives the same result. if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) { compare_to_good_digest(r, codecDigest, bm); } // Cannot continue to decode scanlines beyond the end REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); // Interrupting a scanline decode with a full decode starts from // scratch REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess); const int lines = codec->getScanlines(bm.getAddr(0, 0), 1, 0); if (!isIncomplete) { REPORTER_ASSERT(r, lines == 1); } REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes()) == expectedResult); REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0); REPORTER_ASSERT(r, codec->skipScanlines(1) == 0); } else { REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented); } // The rest of this function tests decoding subsets, and will decode an arbitrary number of // random subsets. // Do not attempt to decode subsets of an image of only once pixel, since there is no // meaningful subset. if (size.width() * size.height() == 1) { return; } SkRandom rand; SkIRect subset; SkCodec::Options opts; opts.fSubset = ⊂ for (int i = 0; i < 5; i++) { subset = generate_random_subset(&rand, size.width(), size.height()); SkASSERT(!subset.isEmpty()); const bool supported = codec->getValidSubset(&subset); REPORTER_ASSERT(r, supported == supportsSubsetDecoding); SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height()); SkBitmap bm; bm.allocPixels(subsetInfo); const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(), &opts, nullptr, nullptr); if (supportsSubsetDecoding) { REPORTER_ASSERT(r, result == expectedResult); // Webp is the only codec that supports subsets, and it will have modified the subset // to have even left/top. REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); } else { // No subsets will work. REPORTER_ASSERT(r, result == SkCodec::kUnimplemented); } } // SkScaledCodec tests if ((supportsScanlineDecoding || supportsSubsetDecoding) && supports_scaled_codec(path)) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(nullptr); if (isIncomplete) { size_t size = stream->getLength(); SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3))); codec.reset(SkScaledCodec::NewFromData(data)); } else { codec.reset(SkScaledCodec::NewFromStream(stream.detach())); } if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } SkBitmap bm; SkMD5::Digest scaledCodecDigest; test_codec(r, codec, bm, info, size, supports565, expectedResult, &scaledCodecDigest, &codecDigest); } // If we've just tested incomplete decodes, let's run the same test again on full decodes. if (isIncomplete) { check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, supports565, false); } }
// static bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkBitmap& source, ResizeMethod method, int destWidth, int destHeight, const SkIRect& destSubset, const SkConvolutionProcs& convolveProcs, SkBitmap::Allocator* allocator) { // Ensure that the ResizeMethod enumeration is sound. SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && (method <= RESIZE_LAST_QUALITY_METHOD)) || ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && (method <= RESIZE_LAST_ALGORITHM_METHOD))); SkIRect dest = { 0, 0, destWidth, destHeight }; if (!dest.contains(destSubset)) { SkErrorInternals::SetError( kInvalidArgument_SkError, "Sorry, you passed me a bitmap resize " " method I have never heard of: %d", method ); } // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just // return empty. if (source.width() < 1 || source.height() < 1 || destWidth < 1 || destHeight < 1) { // todo: seems like we could handle negative dstWidth/Height, since that // is just a negative scale (flip) return false; } method = ResizeMethodToAlgorithmMethod(method); // Check that we deal with an "algorithm methods" from this point onward. SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) && (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD)); SkAutoLockPixels locker(source); if (!source.readyToDraw() || source.config() != SkBitmap::kARGB_8888_Config) { return false; } SkResizeFilter filter(method, source.width(), source.height(), destWidth, destHeight, destSubset, convolveProcs); // Get a source bitmap encompassing this touched area. We construct the // offsets and row strides such that it looks like a new bitmap, while // referring to the old data. const unsigned char* sourceSubset = reinterpret_cast<const unsigned char*>(source.getPixels()); // Convolve into the result. SkBitmap result; result.setConfig(SkBitmap::kARGB_8888_Config, destSubset.width(), destSubset.height(), 0, source.alphaType()); result.allocPixels(allocator, NULL); if (!result.readyToDraw()) { return false; } BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), !source.isOpaque(), filter.xFilter(), filter.yFilter(), static_cast<int>(result.rowBytes()), static_cast<unsigned char*>(result.getPixels()), convolveProcs, true); *resultPtr = result; resultPtr->lockPixels(); SkASSERT(NULL != resultPtr->getPixels()); return true; }
// Basic test of the SkSpecialImage public API (e.g., peekTexture, peekPixels & draw) static void test_image(const sk_sp<SkSpecialImage>& img, skiatest::Reporter* reporter, GrContext* context, bool isGPUBacked, int offset, int size) { const SkIRect subset = img->subset(); REPORTER_ASSERT(reporter, offset == subset.left()); REPORTER_ASSERT(reporter, offset == subset.top()); REPORTER_ASSERT(reporter, kSmallerSize == subset.width()); REPORTER_ASSERT(reporter, kSmallerSize == subset.height()); //-------------- // Test that isTextureBacked reports the correct backing type REPORTER_ASSERT(reporter, isGPUBacked == img->isTextureBacked()); #if SK_SUPPORT_GPU //-------------- // Test asTextureProxyRef - as long as there is a context this should succeed if (context) { sk_sp<GrTextureProxy> proxy(img->asTextureProxyRef(context)); REPORTER_ASSERT(reporter, proxy); } #endif //-------------- // Test getROPixels - this should always succeed regardless of backing store SkBitmap bitmap; REPORTER_ASSERT(reporter, img->getROPixels(&bitmap)); if (context) { REPORTER_ASSERT(reporter, kSmallerSize == bitmap.width()); REPORTER_ASSERT(reporter, kSmallerSize == bitmap.height()); } else { REPORTER_ASSERT(reporter, size == bitmap.width()); REPORTER_ASSERT(reporter, size == bitmap.height()); } //-------------- // Test that draw restricts itself to the subset SkImageFilter::OutputProperties outProps(img->getColorSpace()); sk_sp<SkSpecialSurface> surf(img->makeSurface(outProps, SkISize::Make(kFullSize, kFullSize), kPremul_SkAlphaType)); SkCanvas* canvas = surf->getCanvas(); canvas->clear(SK_ColorBLUE); img->draw(canvas, SkIntToScalar(kPad), SkIntToScalar(kPad), nullptr); SkBitmap bm; bm.allocN32Pixels(kFullSize, kFullSize, false); bool result = canvas->readPixels(bm.info(), bm.getPixels(), bm.rowBytes(), 0, 0); SkASSERT_RELEASE(result); // Only the center (red) portion should've been drawn into the canvas REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kPad-1, kPad-1)); REPORTER_ASSERT(reporter, SK_ColorRED == bm.getColor(kPad, kPad)); REPORTER_ASSERT(reporter, SK_ColorRED == bm.getColor(kSmallerSize+kPad-1, kSmallerSize+kPad-1)); REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kSmallerSize+kPad, kSmallerSize+kPad)); //-------------- // Test that asImage & makeTightSurface return appropriately sized objects // of the correct backing type SkIRect newSubset = SkIRect::MakeWH(subset.width(), subset.height()); { sk_sp<SkImage> tightImg(img->asImage(&newSubset)); REPORTER_ASSERT(reporter, tightImg->width() == subset.width()); REPORTER_ASSERT(reporter, tightImg->height() == subset.height()); REPORTER_ASSERT(reporter, isGPUBacked == tightImg->isTextureBacked()); SkPixmap tmpPixmap; REPORTER_ASSERT(reporter, isGPUBacked != !!tightImg->peekPixels(&tmpPixmap)); } { SkImageFilter::OutputProperties outProps(img->getColorSpace()); sk_sp<SkSurface> tightSurf(img->makeTightSurface(outProps, subset.size())); REPORTER_ASSERT(reporter, tightSurf->width() == subset.width()); REPORTER_ASSERT(reporter, tightSurf->height() == subset.height()); REPORTER_ASSERT(reporter, isGPUBacked == !!tightSurf->getTextureHandle(SkSurface::kDiscardWrite_BackendHandleAccess)); SkPixmap tmpPixmap; REPORTER_ASSERT(reporter, isGPUBacked != !!tightSurf->peekPixels(&tmpPixmap)); } }
static inline void blitrect(SkBlitter* blitter, const SkIRect& r) { blitter->blitRect(r.fLeft, r.fTop, r.width(), r.height()); }
bool SkDisplacementMapEffect::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx, SkBitmap* result, SkIPoint* offset) const { SkBitmap colorBM = src; SkIPoint colorOffset = SkIPoint::Make(0, 0); if (!this->filterInputGPU(1, proxy, src, ctx, &colorBM, &colorOffset)) { return false; } SkBitmap displacementBM = src; SkIPoint displacementOffset = SkIPoint::Make(0, 0); if (!this->filterInputGPU(0, proxy, src, ctx, &displacementBM, &displacementOffset)) { return false; } SkIRect bounds; // Since GrDisplacementMapEffect does bounds checking on color pixel access, we don't need to // pad the color bitmap to bounds here. if (!this->applyCropRect(ctx, colorBM, colorOffset, &bounds)) { return false; } SkIRect displBounds; if (!this->applyCropRect(ctx, proxy, displacementBM, &displacementOffset, &displBounds, &displacementBM)) { return false; } if (!bounds.intersect(displBounds)) { return false; } GrTexture* color = colorBM.getTexture(); GrTexture* displacement = displacementBM.getTexture(); GrContext* context = color->getContext(); GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = bounds.width(); desc.fHeight = bounds.height(); desc.fConfig = kSkia8888_GrPixelConfig; SkAutoTUnref<GrTexture> dst(context->textureProvider()->createApproxTexture(desc)); if (!dst) { return false; } SkVector scale = SkVector::Make(fScale, fScale); ctx.ctm().mapVectors(&scale, 1); GrPaint paint; SkMatrix offsetMatrix = GrCoordTransform::MakeDivByTextureWHMatrix(displacement); offsetMatrix.preTranslate(SkIntToScalar(colorOffset.fX - displacementOffset.fX), SkIntToScalar(colorOffset.fY - displacementOffset.fY)); paint.addColorFragmentProcessor( GrDisplacementMapEffect::Create(fXChannelSelector, fYChannelSelector, scale, displacement, offsetMatrix, color, colorBM.dimensions()))->unref(); paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode); SkIRect colorBounds = bounds; colorBounds.offset(-colorOffset); SkMatrix matrix; matrix.setTranslate(-SkIntToScalar(colorBounds.x()), -SkIntToScalar(colorBounds.y())); SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(dst->asRenderTarget())); if (!drawContext) { return false; } drawContext->drawRect(GrClip::WideOpen(), paint, matrix, SkRect::Make(colorBounds)); offset->fX = bounds.left(); offset->fY = bounds.top(); GrWrapTextureInBitmap(dst, bounds.width(), bounds.height(), false, result); return true; }
// Create the layer information for the hoisted layer and secure the // required texture/render target resources. static void prepare_for_hoisting(GrLayerCache* layerCache, const SkPicture* topLevelPicture, const SkMatrix& initialMat, const SkLayerInfo::BlockInfo& info, const SkIRect& srcIR, const SkIRect& dstIR, SkTDArray<GrHoistedLayer>* needRendering, SkTDArray<GrHoistedLayer>* recycled, bool attemptToAtlas, int numSamples) { const SkPicture* pict = info.fPicture ? info.fPicture : topLevelPicture; GrCachedLayer* layer = layerCache->findLayerOrCreate(topLevelPicture->uniqueID(), SkToInt(info.fSaveLayerOpID), SkToInt(info.fRestoreOpID), srcIR, dstIR, initialMat, info.fKey, info.fKeySize, info.fPaint); GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = srcIR.width(); desc.fHeight = srcIR.height(); desc.fConfig = kSkia8888_GrPixelConfig; desc.fSampleCnt = numSamples; bool locked, needsRendering; if (attemptToAtlas) { locked = layerCache->tryToAtlas(layer, desc, &needsRendering); } else { locked = layerCache->lock(layer, desc, &needsRendering); } if (!locked) { // GPU resources could not be secured for the hoisting of this layer return; } if (attemptToAtlas) { SkASSERT(layer->isAtlased()); } GrHoistedLayer* hl; if (needsRendering) { if (!attemptToAtlas) { SkASSERT(!layer->isAtlased()); } hl = needRendering->append(); } else { hl = recycled->append(); } layerCache->addUse(layer); hl->fLayer = layer; hl->fPicture = pict; hl->fLocalMat = info.fLocalMat; hl->fInitialMat = initialMat; hl->fPreMat = initialMat; hl->fPreMat.preConcat(info.fPreMat); }
bool SkMorphologyImageFilter::filterImageGeneric(SkMorphologyImageFilter::Proc procX, SkMorphologyImageFilter::Proc procY, Proxy* proxy, const SkBitmap& source, const Context& ctx, SkBitmap* dst, SkIPoint* offset) const { SkBitmap src = source; SkIPoint srcOffset = SkIPoint::Make(0, 0); if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctx, &src, &srcOffset)) { return false; } if (src.colorType() != kN32_SkColorType) { return false; } SkIRect bounds; if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) { return false; } SkAutoLockPixels alp(src); if (!src.getPixels()) { return false; } if (!dst->allocPixels(src.info().makeWH(bounds.width(), bounds.height()))) { return false; } SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()), SkIntToScalar(this->radius().height())); ctx.ctm().mapVectors(&radius, 1); int width = SkScalarFloorToInt(radius.fX); int height = SkScalarFloorToInt(radius.fY); if (width < 0 || height < 0) { return false; } SkIRect srcBounds = bounds; srcBounds.offset(-srcOffset); if (width == 0 && height == 0) { src.extractSubset(dst, srcBounds); offset->fX = bounds.left(); offset->fY = bounds.top(); return true; } SkBitmap temp; if (!temp.allocPixels(dst->info())) { return false; } if (width > 0 && height > 0) { callProcX(procX, src, &temp, width, srcBounds); SkIRect tmpBounds = SkIRect::MakeWH(srcBounds.width(), srcBounds.height()); callProcY(procY, temp, dst, height, tmpBounds); } else if (width > 0) { callProcX(procX, src, dst, width, srcBounds); } else if (height > 0) { callProcY(procY, src, dst, height, srcBounds); } offset->fX = bounds.left(); offset->fY = bounds.top(); return true; }