// 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