void draw(SkCanvas* canvas,
           const SkRect& rect,
           const SkSize& deviceSize,
           SkPaint::FilterLevel filterLevel,
           SkImageFilter* input = NULL) {
     SkRect dstRect;
     canvas->getTotalMatrix().mapRect(&dstRect, rect);
     canvas->save();
     SkScalar deviceScaleX = SkScalarDiv(deviceSize.width(), dstRect.width());
     SkScalar deviceScaleY = SkScalarDiv(deviceSize.height(), dstRect.height());
     canvas->translate(rect.x(), rect.y());
     canvas->scale(deviceScaleX, deviceScaleY);
     canvas->translate(-rect.x(), -rect.y());
     SkMatrix matrix;
     matrix.setScale(SkScalarInvert(deviceScaleX),
                     SkScalarInvert(deviceScaleY));
     SkAutoTUnref<SkImageFilter> imageFilter(
         SkMatrixImageFilter::Create(matrix, filterLevel, input));
     SkPaint filteredPaint;
     filteredPaint.setImageFilter(imageFilter.get());
     canvas->saveLayer(&rect, &filteredPaint);
     SkPaint paint;
     paint.setColor(0xFF00FF00);
     SkRect ovalRect = rect;
     ovalRect.inset(SkIntToScalar(4), SkIntToScalar(4));
     canvas->drawOval(ovalRect, paint);
     canvas->restore(); // for saveLayer
     canvas->restore();
 }
Beispiel #2
0
void SkDeferredCanvas::flush_translate(SkScalar* x, SkScalar* y, const SkRect& bounds,
                                       const SkPaint* paint) {
    SkRect tmp = bounds;
    this->flush_check(&tmp, paint, kNoClip_Flag | kNoScale_Flag);
    *x += tmp.x() - bounds.x();
    *y += tmp.y() - bounds.y();
}
void SkMatrixImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
    SkRect bounds = src;
    if (getInput(0)) {
        getInput(0)->computeFastBounds(src, &bounds);
    }
    SkMatrix matrix;
    matrix.setTranslate(-bounds.x(), -bounds.y());
    matrix.postConcat(fTransform);
    matrix.postTranslate(bounds.x(), bounds.y());
    matrix.mapRect(dst, bounds);
}
Beispiel #4
0
void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) {
    // x, y default to 0
    if (rect.x() != 0) {
        this->addAttribute("x", rect.x());
    }
    if (rect.y() != 0) {
        this->addAttribute("y", rect.y());
    }

    this->addAttribute("width", rect.width());
    this->addAttribute("height", rect.height());
}
Beispiel #5
0
void SkDeferredCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
    SkRect modRect = rrect.getBounds();
    this->flush_check(&modRect, &paint, kNoClip_Flag);
    fCanvas->drawRRect(make_offset(rrect,
                                   modRect.x() - rrect.getBounds().x(),
                                   modRect.y() - rrect.getBounds().y()), paint);
}
Beispiel #6
0
/*
 *  Test the 3 annotation types by recording them into a picture, serializing, and then playing
 *  them back into another canvas.
 */
DEF_TEST(Annotations, reporter) {
    SkPictureRecorder recorder;
    SkCanvas* recordingCanvas = recorder.beginRecording(SkRect::MakeWH(100, 100));

    const char* str0 = "rect-with-url";
    const SkRect r0 = SkRect::MakeWH(10, 10);
    sk_sp<SkData> d0(SkData::MakeWithCString(str0));
    SkAnnotateRectWithURL(recordingCanvas, r0, d0.get());

    const char* str1 = "named-destination";
    const SkRect r1 = SkRect::MakeXYWH(5, 5, 0, 0); // collapsed to a point
    sk_sp<SkData> d1(SkData::MakeWithCString(str1));
    SkAnnotateNamedDestination(recordingCanvas, {r1.x(), r1.y()}, d1.get());

    const char* str2 = "link-to-destination";
    const SkRect r2 = SkRect::MakeXYWH(20, 20, 5, 6);
    sk_sp<SkData> d2(SkData::MakeWithCString(str2));
    SkAnnotateLinkToDestination(recordingCanvas, r2, d2.get());

    const AnnotationRec recs[] = {
        { r0, SkAnnotationKeys::URL_Key(),                  std::move(d0) },
        { r1, SkAnnotationKeys::Define_Named_Dest_Key(),    std::move(d1) },
        { r2, SkAnnotationKeys::Link_Named_Dest_Key(),      std::move(d2) },
    };

    sk_sp<SkPicture> pict0(recorder.finishRecordingAsPicture());
    sk_sp<SkPicture> pict1(copy_picture_via_serialization(pict0.get()));

    TestAnnotationCanvas canvas(reporter, recs, SK_ARRAY_COUNT(recs));
    canvas.drawPicture(pict1);
}
static void convolve_gaussian_2d(GrDrawContext* drawContext,
                                 const GrClip& clip,
                                 const SkRect& srcRect,
                                 GrTexture* texture,
                                 int radiusX,
                                 int radiusY,
                                 SkScalar sigmaX,
                                 SkScalar sigmaY,
                                 const SkRect* srcBounds) {
    SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
    SkMatrix localMatrix = SkMatrix::MakeTrans(srcRect.x(), srcRect.y());
    SkISize size = SkISize::Make(2 * radiusX + 1,  2 * radiusY + 1);
    SkIPoint kernelOffset = SkIPoint::Make(radiusX, radiusY);
    GrPaint paint;
    SkIRect bounds;
    if (srcBounds) {
        srcBounds->roundOut(&bounds);
    } else {
        bounds.setEmpty();
    }

    SkAutoTUnref<GrFragmentProcessor> conv(GrMatrixConvolutionEffect::CreateGaussian(
            texture, bounds, size, 1.0, 0.0, kernelOffset,
            srcBounds ? GrTextureDomain::kDecal_Mode : GrTextureDomain::kIgnore_Mode,
            true, sigmaX, sigmaY));
    paint.addColorFragmentProcessor(conv);
    paint.setPorterDuffXPFactory(SkXfermode::kSrc_Mode);
    drawContext->fillRectWithLocalMatrix(clip, paint, SkMatrix::I(), dstRect, localMatrix);
}
static void imagescaleproc(SkCanvas* canvas, SkImage* image, const SkBitmap&, const SkIRect& srcIR,
                           const SkRect& dstR) {
    const int newW = SkScalarRoundToInt(dstR.width());
    const int newH = SkScalarRoundToInt(dstR.height());
    SkAutoTUnref<SkImage> newImage(image->newImage(newW, newH, &srcIR));

#ifdef SK_DEBUG
    const SkIRect baseR = SkIRect::MakeWH(image->width(), image->height());
    const bool containsSubset = baseR.contains(srcIR);
#endif

    if (newImage) {
        SkASSERT(containsSubset);
        canvas->drawImage(newImage, dstR.x(), dstR.y());
    } else {
        // newImage() does not support subsets that are not contained by the original
        // but drawImageRect does, so we just draw an X in its place to indicate that we are
        // deliberately not drawing here.
        SkASSERT(!containsSubset);
        SkPaint paint;
        paint.setStyle(SkPaint::kStroke_Style);
        paint.setStrokeWidth(4);
        canvas->drawLine(4, 4, newW - 4.0f, newH - 4.0f, paint);
        canvas->drawLine(4, newH - 4.0f, newW - 4.0f, 4, paint);
    }
}
SkMagnifierImageFilter::SkMagnifierImageFilter(const SkRect& srcRect, SkScalar inset,
                                               sk_sp<SkImageFilter> input)
    : INHERITED(&input, 1, nullptr)
    , fSrcRect(srcRect)
    , fInset(inset) {
    SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0);
}
Beispiel #10
0
SkImage* SkImage_Base::onNewImage(int newWidth, int newHeight, const SkIRect* subset,
                                  SkFilterQuality quality) const {
    const bool opaque = this->isOpaque();
    const SkImageInfo info = SkImageInfo::Make(newWidth, newHeight, kN32_SkColorType,
                                               opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
    SkAutoTUnref<SkSurface> surface(this->newSurface(info, nullptr));
    if (!surface.get()) {
        return nullptr;
    }

    SkRect src;
    if (subset) {
        src.set(*subset);
    } else {
        src = SkRect::MakeIWH(this->width(), this->height());
    }

    surface->getCanvas()->scale(newWidth / src.width(), newHeight / src.height());
    surface->getCanvas()->translate(-src.x(), -src.y());

    SkPaint paint;
    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    paint.setFilterQuality(quality);
    surface->getCanvas()->drawImage(this, 0, 0, &paint);
    return surface->newImageSnapshot();
}
sk_sp<SkImageFilter> SkTileImageFilter::Make(const SkRect& srcRect, const SkRect& dstRect,
                                             sk_sp<SkImageFilter> input) {
    if (!SkIsValidRect(srcRect) || !SkIsValidRect(dstRect)) {
        return nullptr;
    }
    if (srcRect.width() == dstRect.width() && srcRect.height() == dstRect.height()) {
        SkRect ir = dstRect;
        if (!ir.intersect(srcRect)) {
            return input;
        }
        CropRect cropRect(ir);
        return SkOffsetImageFilter::Make(dstRect.x() - srcRect.x(),
                                         dstRect.y() - srcRect.y(),
                                         std::move(input),
                                         &cropRect);
    }
    return sk_sp<SkImageFilter>(new SkTileImageFilter(srcRect, dstRect, std::move(input)));
}
Beispiel #12
0
// Return true if the rectangle is aligned to integer boundaries.
// See comments for computeBitmapDrawRects() for how this is used.
static bool areBoundariesIntegerAligned(const SkRect& rect)
{
    // Value is 1.19209e-007. This is the tolerance threshold.
    const float epsilon = std::numeric_limits<float>::epsilon();
    SkIRect roundedRect = roundedIntRect(rect);

    return fabs(rect.x() - roundedRect.x()) < epsilon
        && fabs(rect.y() - roundedRect.y()) < epsilon
        && fabs(rect.right() - roundedRect.right()) < epsilon
        && fabs(rect.bottom() - roundedRect.bottom()) < epsilon;
}
    void drawRect(const SkRect& r, SkColor c) override {
        CGContextRef cg = (CGContextRef)fCanvas->accessTopRasterHandle();
        
        CGColorRef color = CGColorCreateGenericRGB(SkColorGetR(c)/255.f,
                                                   SkColorGetG(c)/255.f,
                                                   SkColorGetB(c)/255.f,
                                                   SkColorGetA(c)/255.f);

        CGContextSetFillColorWithColor(cg, color);
        CGContextFillRect(cg, CGRectMake(r.x(), r.y(), r.width(), r.height()));
    }
Beispiel #14
0
void SkDeferredCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y,
                                const SkPaint* paint) {
    const SkScalar w = SkIntToScalar(bitmap.width());
    const SkScalar h = SkIntToScalar(bitmap.height());
    SkRect bounds = SkRect::MakeXYWH(x, y, w, h);
    this->flush_check(&bounds, paint, kNoClip_Flag);
    if (bounds.width() == w && bounds.height() == h) {
        fCanvas->drawBitmap(bitmap, bounds.x(), bounds.y(), paint);
    } else {
        fCanvas->drawBitmapRect(bitmap, bounds, paint);
    }
}
Beispiel #15
0
void SkDeferredCanvas::onDrawImage(const SkImage* image, SkScalar x, SkScalar y,
                                   const SkPaint* paint) {
    const SkScalar w = SkIntToScalar(image->width());
    const SkScalar h = SkIntToScalar(image->height());
    SkRect bounds = SkRect::MakeXYWH(x, y, w, h);
    this->flush_check(&bounds, paint, kNoClip_Flag);
    if (bounds.width() == w && bounds.height() == h) {
        fCanvas->drawImage(image, bounds.x(), bounds.y(), paint);
    } else {
        fCanvas->drawImageRect(image, bounds, paint);
    }
}
Beispiel #16
0
static SkCanvas* trim(SkCanvas* canvas, SkScalar width, SkScalar height,
                      const SkRect* content) {
    if (content && canvas) {
        SkRect inner = *content;
        if (!inner.intersect({0, 0, width, height})) {
            return nullptr;
        }
        canvas->clipRect(inner);
        canvas->translate(inner.x(), inner.y());
    }
    return canvas;
}
Beispiel #17
0
 void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override
 {
     if (rect.width() == 0 && rect.height() == 0) {
         SkPoint point = getTotalMatrix().mapXY(rect.x(), rect.y());
         Operation operation = {
             DrawPoint, SkRect::MakeXYWH(point.x(), point.y(), 0, 0) };
         m_recordedOperations.append(operation);
     } else {
         Operation operation = { DrawRect, rect };
         getTotalMatrix().mapRect(&operation.rect);
         m_recordedOperations.append(operation);
     }
 }
bool DrawingDisplayItem::equals(const DisplayItem& other) const
{
    if (!DisplayItem::equals(other))
        return false;

    RefPtr<const SkPicture> picture = this->picture();
    RefPtr<const SkPicture> otherPicture = static_cast<const DrawingDisplayItem&>(other).picture();

    if (!picture && !otherPicture)
        return true;
    if (!picture || !otherPicture)
        return false;

    switch (getUnderInvalidationCheckingMode()) {
    case DrawingDisplayItem::CheckPicture: {
        if (picture->approximateOpCount() != otherPicture->approximateOpCount())
            return false;

        SkDynamicMemoryWStream pictureSerialized;
        picture->serialize(&pictureSerialized);
        SkDynamicMemoryWStream otherPictureSerialized;
        otherPicture->serialize(&otherPictureSerialized);
        if (pictureSerialized.bytesWritten() != otherPictureSerialized.bytesWritten())
            return false;

        RefPtr<SkData> oldData = adoptRef(otherPictureSerialized.copyToData());
        RefPtr<SkData> newData = adoptRef(pictureSerialized.copyToData());
        return oldData->equals(newData.get());
    }
    case DrawingDisplayItem::CheckBitmap: {
        SkRect rect = picture->cullRect();
        if (rect != otherPicture->cullRect())
            return false;

        SkBitmap bitmap;
        bitmap.allocPixels(SkImageInfo::MakeN32Premul(rect.width(), rect.height()));
        SkCanvas canvas(bitmap);
        canvas.translate(-rect.x(), -rect.y());
        canvas.drawPicture(otherPicture.get());
        SkPaint diffPaint;
        diffPaint.setXfermodeMode(SkXfermode::kDifference_Mode);
        canvas.drawPicture(picture.get(), nullptr, &diffPaint);
        return bitmapIsAllZero(bitmap);
    }
    default:
        ASSERT_NOT_REACHED();
    }
    return false;
}
Beispiel #19
0
// Draws the given bitmap to the given canvas. The subset of the source bitmap
// identified by src_rect is drawn to the given destination rect. The bitmap
// will be resampled to resample_width * resample_height (this is the size of
// the whole image, not the subset). See shouldResampleBitmap for more.
//
// This does a lot of computation to resample only the portion of the bitmap
// that will only be drawn. This is critical for performance since when we are
// scrolling, for example, we are only drawing a small strip of the image.
// Resampling the whole image every time is very slow, so this speeds up things
// dramatically.
//
// Note: this code is only used when the canvas transformation is limited to
// scaling or translation.
static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect)
{
#if PLATFORM(CHROMIUM)
    TRACE_EVENT0("skia", "drawResampledBitmap");
#endif
    // Apply forward transform to destRect to estimate required size of
    // re-sampled bitmap, and use only in calls required to resize, or that
    // check for the required size.
    SkRect destRectTransformed;
    canvas.getTotalMatrix().mapRect(&destRectTransformed, destRect);
    SkIRect destRectTransformedRounded;
    destRectTransformed.round(&destRectTransformedRounded);

    // Compute the visible portion of our rect.
    SkRect destRectVisibleSubset;
    ClipRectToCanvas(canvas, destRect, &destRectVisibleSubset);
    // ClipRectToCanvas often overshoots, resulting in a larger region than our
    // original destRect. Intersecting gets us back inside.
    if (!destRectVisibleSubset.intersect(destRect))
        return; // Nothing visible in destRect.

    // Compute the image-relative (bitmap space) subset.
    SkRect destBitmapSubset = destRectVisibleSubset;
    destBitmapSubset.offset(-destRect.x(), -destRect.y());

    // Scale the subset to the requested size. The canvas scale can be negative,
    // but the resampling code is only interested in positive scaling in its normal space.
    SkMatrix subsetTransform;
    subsetTransform.setScale(SkScalarAbs(canvas.getTotalMatrix().getScaleX()),
                             SkScalarAbs(canvas.getTotalMatrix().getScaleY()));
    SkRect destBitmapSubsetTransformed;
    subsetTransform.mapRect(&destBitmapSubsetTransformed, destBitmapSubset);
    SkIRect destBitmapSubsetTransformedRounded;
    destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded);

    // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded
    // to go outside the image, so need to clip to avoid problems.
    if (!destBitmapSubsetTransformedRounded.intersect(
            0, 0, destRectTransformedRounded.width(), destRectTransformedRounded.height()))
        return; // Image is not visible.

    SkBitmap resampled = bitmap.resizedBitmap(srcIRect,
                                              destRectTransformedRounded.width(),
                                              destRectTransformedRounded.height(),
                                              destBitmapSubsetTransformedRounded);
    canvas.drawBitmapRect(resampled, 0, destRectVisibleSubset, &paint);
}
bool SkMatrixImageFilter::onFilterImage(Proxy* proxy,
                                        const SkBitmap& source,
                                        const Context& ctx,
                                        SkBitmap* result,
                                        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;
    }

    SkRect dstRect;
    SkIRect srcBounds, dstBounds;
    src.getBounds(&srcBounds);
    srcBounds.offset(srcOffset);
    SkRect srcRect = SkRect::Make(srcBounds);
    SkMatrix matrix;
    if (!ctx.ctm().invert(&matrix)) {
        return false;
    }
    matrix.postConcat(fTransform);
    matrix.postConcat(ctx.ctm());
    matrix.mapRect(&dstRect, srcRect);
    dstRect.roundOut(&dstBounds);

    SkAutoTUnref<SkBaseDevice> device(proxy->createDevice(dstBounds.width(), dstBounds.height()));
    if (NULL == device.get()) {
        return false;
    }

    SkCanvas canvas(device.get());
    canvas.translate(-SkIntToScalar(dstBounds.x()), -SkIntToScalar(dstBounds.y()));
    canvas.concat(matrix);
    SkPaint paint;

    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    paint.setFilterLevel(fFilterLevel);
    canvas.drawBitmap(src, srcRect.x(), srcRect.y(), &paint);

    *result = device.get()->accessBitmap(false);
    offset->fX = dstBounds.fLeft;
    offset->fY = dstBounds.fTop;
    return true;
}
Beispiel #21
0
void SkDeferredCanvas::flush_translate(SkScalar* x, SkScalar* y, const SkPaint& paint) {
    SkRect tmp = SkRect::MakeXYWH(*x, *y, 1, 1);
    this->flush_check(&tmp, &paint, kNoClip_Flag | kNoCull_Flag | kNoScale_Flag);
    *x = tmp.x();
    *y = tmp.y();
}
Beispiel #22
0
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) {
        SkShader::BitmapType bitmapType;
        SkMatrix matrix;
        bitmapType = shader.asABitmap(&fImage, &matrix, fImageTileModes);
        if (bitmapType != SkShader::kDefault_BitmapType) {
            // 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(SkScalarDiv(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());
        } else {
            SkASSERT(matrix.isIdentity());
        }
        fPixelGeneration = fImage.getGenerationID();
    } else {
        AllocateGradientInfoStorage();
        shader.asAGradient(&fInfo);
    }
}
SkMagnifierImageFilter::SkMagnifierImageFilter(const SkRect& srcRect, SkScalar inset,
                                               SkImageFilter* input)
    : INHERITED(1, &input), fSrcRect(srcRect), fInset(inset) {
    SkASSERT(srcRect.x() >= 0 && srcRect.y() >= 0 && inset >= 0);
}
void DisplayItemList::checkCachedDisplayItemIsUnchanged(const DisplayItem& displayItem, DisplayItemIndicesByClientMap& displayItemIndicesByClient)
{
    ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled());

    if (!displayItem.isDrawing() || displayItem.skippedCache() || !clientCacheIsValid(displayItem.client()))
        return;

    // If checking under-invalidation, we always generate new display item even if the client is not invalidated.
    // Checks if the new picture is the same as the cached old picture. If the new picture is different but
    // the client is not invalidated, issue error about under-invalidation.
    size_t index = findMatchingItemFromIndex(displayItem.nonCachedId(), displayItemIndicesByClient, m_currentDisplayItems);
    if (index == kNotFound) {
        showUnderInvalidationError("ERROR: under-invalidation: no cached display item", displayItem);
        ASSERT_NOT_REACHED();
        return;
    }

    DisplayItems::iterator foundItem = m_currentDisplayItems.begin() + index;
    RefPtr<const SkPicture> newPicture = static_cast<const DrawingDisplayItem&>(displayItem).picture();
    RefPtr<const SkPicture> oldPicture = static_cast<const DrawingDisplayItem&>(*foundItem).picture();
    // Invalidate the display item so that we can check if there are any remaining cached display items after merging.
    foundItem->clearClientForUnderInvalidationChecking();

    if (!newPicture && !oldPicture)
        return;
    if (newPicture && oldPicture) {
        switch (static_cast<const DrawingDisplayItem&>(displayItem).underInvalidationCheckingMode()) {
        case DrawingDisplayItem::CheckPicture:
            if (newPicture->approximateOpCount() == oldPicture->approximateOpCount()) {
                SkDynamicMemoryWStream newPictureSerialized;
                newPicture->serialize(&newPictureSerialized);
                SkDynamicMemoryWStream oldPictureSerialized;
                oldPicture->serialize(&oldPictureSerialized);

                if (newPictureSerialized.bytesWritten() == oldPictureSerialized.bytesWritten()) {
                    RefPtr<SkData> oldData = adoptRef(oldPictureSerialized.copyToData());
                    RefPtr<SkData> newData = adoptRef(newPictureSerialized.copyToData());
                    if (oldData->equals(newData.get()))
                        return;
                }
            }
            break;
        case DrawingDisplayItem::CheckBitmap:
            if (newPicture->cullRect() == oldPicture->cullRect()) {
                SkBitmap bitmap;
                SkRect rect = newPicture->cullRect();
                bitmap.allocPixels(SkImageInfo::MakeN32Premul(rect.width(), rect.height()));
                SkCanvas canvas(bitmap);
                canvas.translate(-rect.x(), -rect.y());
                canvas.drawPicture(oldPicture.get());
                SkPaint diffPaint;
                diffPaint.setXfermodeMode(SkXfermode::kDifference_Mode);
                canvas.drawPicture(newPicture.get(), nullptr, &diffPaint);
                if (bitmapIsAllZero(bitmap)) // Contents are the same.
                    return;
            }
        default:
            ASSERT_NOT_REACHED();
        }
    }

    showUnderInvalidationError("ERROR: under-invalidation: display item changed", displayItem);
#ifndef NDEBUG
    String oldPictureDebugString = oldPicture ? pictureAsDebugString(oldPicture.get()) : "None";
    String newPictureDebugString = newPicture ? pictureAsDebugString(newPicture.get()) : "None";
    WTFLogAlways("old picture:\n%s\n", oldPictureDebugString.utf8().data());
    WTFLogAlways("new picture:\n%s\n", newPictureDebugString.utf8().data());
#endif // NDEBUG

    ASSERT_NOT_REACHED();
}