| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "skia/ext/analysis_canvas.h" |
| #include "third_party/skia/include/core/SkDevice.h" |
| #include "third_party/skia/include/core/SkDraw.h" |
| #include "third_party/skia/include/core/SkRRect.h" |
| #include "third_party/skia/include/core/SkShader.h" |
| #include "third_party/skia/src/core/SkRasterClip.h" |
| #include "ui/gfx/rect_conversions.h" |
| |
| namespace { |
| |
| const int kNoLayer = -1; |
| |
| bool IsSolidColorPaint(const SkPaint& paint) { |
| SkXfermode::Mode xfermode; |
| |
| // getXfermode can return a NULL, but that is handled |
| // gracefully by AsMode (NULL turns into kSrcOver mode). |
| SkXfermode::AsMode(paint.getXfermode(), &xfermode); |
| |
| // Paint is solid color if the following holds: |
| // - Alpha is 1.0, style is fill, and there are no special effects |
| // - Xfer mode is either kSrc or kSrcOver (kSrcOver is equivalent |
| // to kSrc if source alpha is 1.0, which is already checked). |
| return (paint.getAlpha() == 255 && |
| !paint.getShader() && |
| !paint.getLooper() && |
| !paint.getMaskFilter() && |
| !paint.getColorFilter() && |
| paint.getStyle() == SkPaint::kFill_Style && |
| (xfermode == SkXfermode::kSrc_Mode || |
| xfermode == SkXfermode::kSrcOver_Mode)); |
| } |
| |
| bool IsFullQuad(const SkDraw& draw, |
| const SkRect& canvas_rect, |
| const SkRect& drawn_rect) { |
| |
| // If the transform results in a non-axis aligned |
| // rect, then be conservative and return false. |
| if (!draw.fMatrix->rectStaysRect()) |
| return false; |
| |
| SkRect draw_bitmap_rect; |
| draw.fBitmap->getBounds(&draw_bitmap_rect); |
| SkRect clip_rect = SkRect::Make(draw.fRC->getBounds()); |
| SkRect device_rect; |
| draw.fMatrix->mapRect(&device_rect, drawn_rect); |
| |
| // The drawn rect covers the full canvas, if the following conditions hold: |
| // - Clip rect is an actual rectangle. |
| // - The rect we're drawing (post-transform) contains the clip rect. |
| // That is, all of clip rect will be colored by the rect. |
| // - Clip rect contains the canvas rect. |
| // That is, we're not clipping to a portion of this canvas. |
| // - The bitmap into which the draw call happens is at least as |
| // big as the canvas rect |
| return draw.fRC->isRect() && |
| device_rect.contains(clip_rect) && |
| clip_rect.contains(canvas_rect) && |
| draw_bitmap_rect.contains(canvas_rect); |
| } |
| |
| } // namespace |
| |
| namespace skia { |
| |
| AnalysisDevice::AnalysisDevice(const SkBitmap& bitmap) |
| : INHERITED(bitmap), |
| is_forced_not_solid_(false), |
| is_forced_not_transparent_(false), |
| is_solid_color_(true), |
| is_transparent_(true), |
| has_text_(false) {} |
| |
| AnalysisDevice::~AnalysisDevice() {} |
| |
| bool AnalysisDevice::GetColorIfSolid(SkColor* color) const { |
| if (is_transparent_) { |
| *color = SK_ColorTRANSPARENT; |
| return true; |
| } |
| if (is_solid_color_) { |
| *color = color_; |
| return true; |
| } |
| return false; |
| } |
| |
| bool AnalysisDevice::HasText() const { |
| return has_text_; |
| } |
| |
| void AnalysisDevice::SetForceNotSolid(bool flag) { |
| is_forced_not_solid_ = flag; |
| if (is_forced_not_solid_) |
| is_solid_color_ = false; |
| } |
| |
| void AnalysisDevice::SetForceNotTransparent(bool flag) { |
| is_forced_not_transparent_ = flag; |
| if (is_forced_not_transparent_) |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::clear(SkColor color) { |
| is_transparent_ = (!is_forced_not_transparent_ && SkColorGetA(color) == 0); |
| has_text_ = false; |
| |
| if (!is_forced_not_solid_ && SkColorGetA(color) == 255) { |
| is_solid_color_ = true; |
| color_ = color; |
| } else { |
| is_solid_color_ = false; |
| } |
| } |
| |
| void AnalysisDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawPoints(const SkDraw& draw, |
| SkCanvas::PointMode mode, |
| size_t count, |
| const SkPoint points[], |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawRect(const SkDraw& draw, |
| const SkRect& rect, |
| const SkPaint& paint) { |
| bool does_cover_canvas = |
| IsFullQuad(draw, SkRect::MakeWH(width(), height()), rect); |
| |
| SkXfermode::Mode xfermode; |
| SkXfermode::AsMode(paint.getXfermode(), &xfermode); |
| |
| // This canvas will become transparent if the following holds: |
| // - The quad is a full tile quad |
| // - We're not in "forced not transparent" mode |
| // - Transfer mode is clear (0 color, 0 alpha) |
| // |
| // If the paint alpha is not 0, or if the transfrer mode is |
| // not src, then this canvas will not be transparent. |
| // |
| // In all other cases, we keep the current transparent value |
| if (does_cover_canvas && |
| !is_forced_not_transparent_ && |
| xfermode == SkXfermode::kClear_Mode) { |
| is_transparent_ = true; |
| has_text_ = false; |
| } else if (paint.getAlpha() != 0 || xfermode != SkXfermode::kSrc_Mode) { |
| is_transparent_ = false; |
| } |
| |
| // This bitmap is solid if and only if the following holds. |
| // Note that this might be overly conservative: |
| // - We're not in "forced not solid" mode |
| // - Paint is solid color |
| // - The quad is a full tile quad |
| if (!is_forced_not_solid_ && IsSolidColorPaint(paint) && does_cover_canvas) { |
| is_solid_color_ = true; |
| color_ = paint.getColor(); |
| has_text_ = false; |
| } else { |
| is_solid_color_ = false; |
| } |
| } |
| |
| void AnalysisDevice::drawOval(const SkDraw& draw, |
| const SkRect& oval, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawRRect(const SkDraw& draw, |
| const SkRRect& rr, |
| const SkPaint& paint) { |
| // This should add the SkRRect to an SkPath, and call |
| // drawPath, but since drawPath ignores the SkPath, just |
| // do the same work here. |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawPath(const SkDraw& draw, |
| const SkPath& path, |
| const SkPaint& paint, |
| const SkMatrix* pre_path_matrix, |
| bool path_is_mutable) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawBitmap(const SkDraw& draw, |
| const SkBitmap& bitmap, |
| const SkMatrix& matrix, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawSprite(const SkDraw& draw, |
| const SkBitmap& bitmap, |
| int x, |
| int y, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawBitmapRect(const SkDraw& draw, |
| const SkBitmap& bitmap, |
| const SkRect* src_or_null, |
| const SkRect& dst, |
| const SkPaint& paint, |
| SkCanvas::DrawBitmapRectFlags flags) { |
| // Call drawRect to determine transparency, |
| // but reset solid color to false. |
| drawRect(draw, dst, paint); |
| is_solid_color_ = false; |
| } |
| |
| void AnalysisDevice::drawText(const SkDraw& draw, |
| const void* text, |
| size_t len, |
| SkScalar x, |
| SkScalar y, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| has_text_ = true; |
| } |
| |
| void AnalysisDevice::drawPosText(const SkDraw& draw, |
| const void* text, |
| size_t len, |
| const SkScalar pos[], |
| SkScalar const_y, |
| int scalars_per_pos, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| has_text_ = true; |
| } |
| |
| void AnalysisDevice::drawTextOnPath(const SkDraw& draw, |
| const void* text, |
| size_t len, |
| const SkPath& path, |
| const SkMatrix* matrix, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| has_text_ = true; |
| } |
| |
| #ifdef SK_BUILD_FOR_ANDROID |
| void AnalysisDevice::drawPosTextOnPath(const SkDraw& draw, |
| const void* text, |
| size_t len, |
| const SkPoint pos[], |
| const SkPaint& paint, |
| const SkPath& path, |
| const SkMatrix* matrix) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| has_text_ = true; |
| } |
| #endif |
| |
| void AnalysisDevice::drawVertices(const SkDraw& draw, |
| SkCanvas::VertexMode, |
| int vertex_count, |
| const SkPoint verts[], |
| const SkPoint texs[], |
| const SkColor colors[], |
| SkXfermode* xmode, |
| const uint16_t indices[], |
| int index_count, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| void AnalysisDevice::drawDevice(const SkDraw& draw, |
| SkBaseDevice* device, |
| int x, |
| int y, |
| const SkPaint& paint) { |
| is_solid_color_ = false; |
| is_transparent_ = false; |
| } |
| |
| AnalysisCanvas::AnalysisCanvas(AnalysisDevice* device) |
| : INHERITED(device), |
| saved_stack_size_(0), |
| force_not_solid_stack_level_(kNoLayer), |
| force_not_transparent_stack_level_(kNoLayer) {} |
| |
| AnalysisCanvas::~AnalysisCanvas() {} |
| |
| bool AnalysisCanvas::GetColorIfSolid(SkColor* color) const { |
| return (static_cast<AnalysisDevice*>(getDevice()))->GetColorIfSolid(color); |
| } |
| |
| bool AnalysisCanvas::HasText() const { |
| return (static_cast<AnalysisDevice*>(getDevice()))->HasText(); |
| } |
| |
| bool AnalysisCanvas::abortDrawing() { |
| // Early out as soon as we have detected that the tile has text. |
| return HasText(); |
| } |
| |
| bool AnalysisCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool do_aa) { |
| return INHERITED::clipRect(rect, op, do_aa); |
| } |
| |
| bool AnalysisCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool do_aa) { |
| // clipPaths can make our calls to IsFullQuad invalid (ie have false |
| // positives). As a precaution, force the setting to be non-solid |
| // and non-transparent until we pop this |
| if (force_not_solid_stack_level_ == kNoLayer) { |
| force_not_solid_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); |
| } |
| if (force_not_transparent_stack_level_ == kNoLayer) { |
| force_not_transparent_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); |
| } |
| |
| return INHERITED::clipRect(path.getBounds(), op, do_aa); |
| } |
| |
| bool AnalysisCanvas::clipRRect(const SkRRect& rrect, |
| SkRegion::Op op, |
| bool do_aa) { |
| // clipRRect can make our calls to IsFullQuad invalid (ie have false |
| // positives). As a precaution, force the setting to be non-solid |
| // and non-transparent until we pop this |
| if (force_not_solid_stack_level_ == kNoLayer) { |
| force_not_solid_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); |
| } |
| if (force_not_transparent_stack_level_ == kNoLayer) { |
| force_not_transparent_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); |
| } |
| |
| return INHERITED::clipRect(rrect.getBounds(), op, do_aa); |
| } |
| |
| int AnalysisCanvas::save(SkCanvas::SaveFlags flags) { |
| ++saved_stack_size_; |
| return INHERITED::save(flags); |
| } |
| |
| int AnalysisCanvas::saveLayer(const SkRect* bounds, |
| const SkPaint* paint, |
| SkCanvas::SaveFlags flags) { |
| ++saved_stack_size_; |
| |
| // If after we draw to the saved layer, we have to blend with the current |
| // layer, then we can conservatively say that the canvas will not be of |
| // solid color. |
| if ((paint && !IsSolidColorPaint(*paint)) || |
| (bounds && !bounds->contains(SkRect::MakeWH(getDevice()->width(), |
| getDevice()->height())))) { |
| if (force_not_solid_stack_level_ == kNoLayer) { |
| force_not_solid_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); |
| } |
| } |
| |
| // If after we draw to the save layer, we have to blend with the current |
| // layer using any part of the current layer's alpha, then we can |
| // conservatively say that the canvas will not be transparent. |
| SkXfermode::Mode xfermode = SkXfermode::kSrc_Mode; |
| if (paint) |
| SkXfermode::AsMode(paint->getXfermode(), &xfermode); |
| if (xfermode != SkXfermode::kSrc_Mode) { |
| if (force_not_transparent_stack_level_ == kNoLayer) { |
| force_not_transparent_stack_level_ = saved_stack_size_; |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); |
| } |
| } |
| |
| // Actually saving a layer here could cause a new bitmap to be created |
| // and real rendering to occur. |
| int count = INHERITED::save(flags); |
| if (bounds) { |
| INHERITED::clipRectBounds(bounds, flags, NULL); |
| } |
| return count; |
| } |
| |
| void AnalysisCanvas::restore() { |
| INHERITED::restore(); |
| |
| DCHECK(saved_stack_size_); |
| if (saved_stack_size_) { |
| --saved_stack_size_; |
| if (saved_stack_size_ < force_not_solid_stack_level_) { |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(false); |
| force_not_solid_stack_level_ = kNoLayer; |
| } |
| if (saved_stack_size_ < force_not_transparent_stack_level_) { |
| (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent( |
| false); |
| force_not_transparent_stack_level_ = kNoLayer; |
| } |
| } |
| } |
| |
| } // namespace skia |
| |
| |