// Copyright 2012 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 "cc/resources/picture.h"
#include <algorithm>
#include <limits>
#include <set>
#include "base/base64.h"
#include "base/debug/trace_event.h"
#include "base/debug/trace_event_argument.h"
#include "base/values.h"
#include "cc/base/math_util.h"
#include "cc/base/util.h"
#include "cc/debug/traced_picture.h"
#include "cc/debug/traced_value.h"
#include "cc/layers/content_layer_client.h"
#include "skia/ext/pixel_ref_utils.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkDrawFilter.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/utils/SkNullCanvas.h"
#include "third_party/skia/include/utils/SkPictureUtils.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/skia_util.h"
namespace cc {
namespace {
SkData* EncodeBitmap(size_t* offset, const SkBitmap& bm) {
const int kJpegQuality = 80;
std::vector<unsigned char> data;
// If bitmap is opaque, encode as JPEG.
// Otherwise encode as PNG.
bool encoding_succeeded = false;
if (bm.isOpaque()) {
SkAutoLockPixels lock_bitmap(bm);
if (bm.empty())
return NULL;
encoding_succeeded = gfx::JPEGCodec::Encode(
reinterpret_cast<unsigned char*>(bm.getAddr32(0, 0)),
gfx::JPEGCodec::FORMAT_SkBitmap,
bm.width(),
bm.height(),
bm.rowBytes(),
kJpegQuality,
&data);
} else {
encoding_succeeded = gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &data);
}
if (encoding_succeeded) {
*offset = 0;
return SkData::NewWithCopy(&data.front(), data.size());
}
return NULL;
}
bool DecodeBitmap(const void* buffer, size_t size, SkBitmap* bm) {
const unsigned char* data = static_cast<const unsigned char *>(buffer);
// Try PNG first.
if (gfx::PNGCodec::Decode(data, size, bm))
return true;
// Try JPEG.
scoped_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodec::Decode(data, size));
if (decoded_jpeg) {
*bm = *decoded_jpeg;
return true;
}
return false;
}
} // namespace
scoped_refptr<Picture> Picture::Create(
const gfx::Rect& layer_rect,
ContentLayerClient* client,
const SkTileGridFactory::TileGridInfo& tile_grid_info,
bool gather_pixel_refs,
RecordingMode recording_mode) {
scoped_refptr<Picture> picture = make_scoped_refptr(new Picture(layer_rect));
picture->Record(client, tile_grid_info, recording_mode);
if (gather_pixel_refs)
picture->GatherPixelRefs(tile_grid_info);
return picture;
}
Picture::Picture(const gfx::Rect& layer_rect)
: layer_rect_(layer_rect),
cell_size_(layer_rect.size()) {
// Instead of recording a trace event for object creation here, we wait for
// the picture to be recorded in Picture::Record.
}
scoped_refptr<Picture> Picture::CreateFromSkpValue(const base::Value* value) {
// Decode the picture from base64.
std::string encoded;
if (!value->GetAsString(&encoded))
return NULL;
std::string decoded;
base::Base64Decode(encoded, &decoded);
SkMemoryStream stream(decoded.data(), decoded.size());
// Read the picture. This creates an empty picture on failure.
SkPicture* skpicture = SkPicture::CreateFromStream(&stream, &DecodeBitmap);
if (skpicture == NULL)
return NULL;
gfx::Rect layer_rect(skpicture->width(), skpicture->height());
return make_scoped_refptr(new Picture(skpicture, layer_rect));
}
scoped_refptr<Picture> Picture::CreateFromValue(const base::Value* raw_value) {
const base::DictionaryValue* value = NULL;
if (!raw_value->GetAsDictionary(&value))
return NULL;
// Decode the picture from base64.
std::string encoded;
if (!value->GetString("skp64", &encoded))
return NULL;
std::string decoded;
base::Base64Decode(encoded, &decoded);
SkMemoryStream stream(decoded.data(), decoded.size());
const base::Value* layer_rect_value = NULL;
if (!value->Get("params.layer_rect", &layer_rect_value))
return NULL;
gfx::Rect layer_rect;
if (!MathUtil::FromValue(layer_rect_value, &layer_rect))
return NULL;
// Read the picture. This creates an empty picture on failure.
SkPicture* skpicture = SkPicture::CreateFromStream(&stream, &DecodeBitmap);
if (skpicture == NULL)
return NULL;
return make_scoped_refptr(new Picture(skpicture, layer_rect));
}
Picture::Picture(SkPicture* picture, const gfx::Rect& layer_rect)
: layer_rect_(layer_rect),
picture_(skia::AdoptRef(picture)),
cell_size_(layer_rect.size()) {
}
Picture::Picture(const skia::RefPtr<SkPicture>& picture,
const gfx::Rect& layer_rect,
const PixelRefMap& pixel_refs) :
layer_rect_(layer_rect),
picture_(picture),
pixel_refs_(pixel_refs),
cell_size_(layer_rect.size()) {
}
Picture::~Picture() {
TRACE_EVENT_OBJECT_DELETED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Picture", this);
}
bool Picture::IsSuitableForGpuRasterization() const {
DCHECK(picture_);
// TODO(alokp): SkPicture::suitableForGpuRasterization needs a GrContext.
// Ideally this GrContext should be the same as that for rasterizing this
// picture. But we are on the main thread while the rasterization context
// may be on the compositor or raster thread.
// SkPicture::suitableForGpuRasterization is not implemented yet.
// Pass a NULL context for now and discuss with skia folks if the context
// is really needed.
return picture_->suitableForGpuRasterization(NULL);
}
int Picture::ApproximateOpCount() const {
DCHECK(picture_);
return picture_->approximateOpCount();
}
bool Picture::HasText() const {
DCHECK(picture_);
return picture_->hasText();
}
void Picture::Record(ContentLayerClient* painter,
const SkTileGridFactory::TileGridInfo& tile_grid_info,
RecordingMode recording_mode) {
TRACE_EVENT2("cc",
"Picture::Record",
"data",
AsTraceableRecordData(),
"recording_mode",
recording_mode);
DCHECK(!picture_);
DCHECK(!tile_grid_info.fTileInterval.isEmpty());
SkTileGridFactory factory(tile_grid_info);
SkPictureRecorder recorder;
scoped_ptr<EXPERIMENTAL::SkRecording> recording;
skia::RefPtr<SkCanvas> canvas;
canvas = skia::SharePtr(recorder.beginRecording(
layer_rect_.width(), layer_rect_.height(), &factory));
ContentLayerClient::GraphicsContextStatus graphics_context_status =
ContentLayerClient::GRAPHICS_CONTEXT_ENABLED;
switch (recording_mode) {
case RECORD_NORMALLY:
// Already setup for normal recording.
break;
case RECORD_WITH_SK_NULL_CANVAS:
canvas = skia::AdoptRef(SkCreateNullCanvas());
break;
case RECORD_WITH_PAINTING_DISABLED:
// We pass a disable flag through the paint calls when perfromance
// testing (the only time this case should ever arise) when we want to
// prevent the Blink GraphicsContext object from consuming any compute
// time.
canvas = skia::AdoptRef(SkCreateNullCanvas());
graphics_context_status = ContentLayerClient::GRAPHICS_CONTEXT_DISABLED;
break;
case RECORD_WITH_SKRECORD:
recording.reset(new EXPERIMENTAL::SkRecording(layer_rect_.width(),
layer_rect_.height()));
canvas = skia::SharePtr(recording->canvas());
break;
default:
NOTREACHED();
}
canvas->save();
canvas->translate(SkFloatToScalar(-layer_rect_.x()),
SkFloatToScalar(-layer_rect_.y()));
SkRect layer_skrect = SkRect::MakeXYWH(layer_rect_.x(),
layer_rect_.y(),
layer_rect_.width(),
layer_rect_.height());
canvas->clipRect(layer_skrect);
painter->PaintContents(canvas.get(), layer_rect_, graphics_context_status);
canvas->restore();
picture_ = skia::AdoptRef(recorder.endRecording());
DCHECK(picture_);
if (recording) {
// SkRecording requires it's the only one holding onto canvas before we
// may call releasePlayback(). (This helps enforce thread-safety.)
canvas.clear();
playback_.reset(recording->releasePlayback());
}
EmitTraceSnapshot();
}
void Picture::GatherPixelRefs(
const SkTileGridFactory::TileGridInfo& tile_grid_info) {
TRACE_EVENT2("cc", "Picture::GatherPixelRefs",
"width", layer_rect_.width(),
"height", layer_rect_.height());
DCHECK(picture_);
DCHECK(pixel_refs_.empty());
if (!WillPlayBackBitmaps())
return;
cell_size_ = gfx::Size(
tile_grid_info.fTileInterval.width() +
2 * tile_grid_info.fMargin.width(),
tile_grid_info.fTileInterval.height() +
2 * tile_grid_info.fMargin.height());
DCHECK_GT(cell_size_.width(), 0);
DCHECK_GT(cell_size_.height(), 0);
int min_x = std::numeric_limits<int>::max();
int min_y = std::numeric_limits<int>::max();
int max_x = 0;
int max_y = 0;
skia::DiscardablePixelRefList pixel_refs;
skia::PixelRefUtils::GatherDiscardablePixelRefs(picture_.get(), &pixel_refs);
for (skia::DiscardablePixelRefList::const_iterator it = pixel_refs.begin();
it != pixel_refs.end();
++it) {
gfx::Point min(
RoundDown(static_cast<int>(it->pixel_ref_rect.x()),
cell_size_.width()),
RoundDown(static_cast<int>(it->pixel_ref_rect.y()),
cell_size_.height()));
gfx::Point max(
RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.right())),
cell_size_.width()),
RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.bottom())),
cell_size_.height()));
for (int y = min.y(); y <= max.y(); y += cell_size_.height()) {
for (int x = min.x(); x <= max.x(); x += cell_size_.width()) {
PixelRefMapKey key(x, y);
pixel_refs_[key].push_back(it->pixel_ref);
}
}
min_x = std::min(min_x, min.x());
min_y = std::min(min_y, min.y());
max_x = std::max(max_x, max.x());
max_y = std::max(max_y, max.y());
}
min_pixel_cell_ = gfx::Point(min_x, min_y);
max_pixel_cell_ = gfx::Point(max_x, max_y);
}
int Picture::Raster(SkCanvas* canvas,
SkDrawPictureCallback* callback,
const Region& negated_content_region,
float contents_scale) const {
TRACE_EVENT_BEGIN1(
"cc",
"Picture::Raster",
"data",
AsTraceableRasterData(contents_scale));
DCHECK(picture_);
canvas->save();
for (Region::Iterator it(negated_content_region); it.has_rect(); it.next())
canvas->clipRect(gfx::RectToSkRect(it.rect()), SkRegion::kDifference_Op);
canvas->scale(contents_scale, contents_scale);
canvas->translate(layer_rect_.x(), layer_rect_.y());
if (playback_) {
playback_->draw(canvas);
} else if (callback) {
// If we have a callback, we need to call |draw()|, |drawPicture()| doesn't
// take a callback. This is used by |AnalysisCanvas| to early out.
picture_->draw(canvas, callback);
} else {
// Prefer to call |drawPicture()| on the canvas since it could place the
// entire picture on the canvas instead of parsing the skia operations.
canvas->drawPicture(picture_.get());
}
SkIRect bounds;
canvas->getClipDeviceBounds(&bounds);
canvas->restore();
TRACE_EVENT_END1(
"cc", "Picture::Raster",
"num_pixels_rasterized", bounds.width() * bounds.height());
return bounds.width() * bounds.height();
}
void Picture::Replay(SkCanvas* canvas) {
TRACE_EVENT_BEGIN0("cc", "Picture::Replay");
DCHECK(picture_);
if (playback_) {
playback_->draw(canvas);
} else {
picture_->draw(canvas);
}
SkIRect bounds;
canvas->getClipDeviceBounds(&bounds);
TRACE_EVENT_END1("cc", "Picture::Replay",
"num_pixels_replayed", bounds.width() * bounds.height());
}
scoped_ptr<base::Value> Picture::AsValue() const {
SkDynamicMemoryWStream stream;
if (playback_) {
// SkPlayback can't serialize itself, so re-record into an SkPicture.
SkPictureRecorder recorder;
skia::RefPtr<SkCanvas> canvas(skia::SharePtr(recorder.beginRecording(
layer_rect_.width(),
layer_rect_.height(),
NULL))); // Default (no) bounding-box hierarchy is fastest.
playback_->draw(canvas.get());
skia::RefPtr<SkPicture> picture(skia::AdoptRef(recorder.endRecording()));
picture->serialize(&stream, &EncodeBitmap);
} else {
// Serialize the picture.
picture_->serialize(&stream, &EncodeBitmap);
}
// Encode the picture as base64.
scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
res->Set("params.layer_rect", MathUtil::AsValue(layer_rect_).release());
size_t serialized_size = stream.bytesWritten();
scoped_ptr<char[]> serialized_picture(new char[serialized_size]);
stream.copyTo(serialized_picture.get());
std::string b64_picture;
base::Base64Encode(std::string(serialized_picture.get(), serialized_size),
&b64_picture);
res->SetString("skp64", b64_picture);
return res.PassAs<base::Value>();
}
void Picture::EmitTraceSnapshot() const {
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("cc.debug") "," TRACE_DISABLED_BY_DEFAULT(
"devtools.timeline.picture"),
"cc::Picture",
this,
TracedPicture::AsTraceablePicture(this));
}
void Picture::EmitTraceSnapshotAlias(Picture* original) const {
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("cc.debug") "," TRACE_DISABLED_BY_DEFAULT(
"devtools.timeline.picture"),
"cc::Picture",
this,
TracedPicture::AsTraceablePictureAlias(original));
}
base::LazyInstance<Picture::PixelRefs>
Picture::PixelRefIterator::empty_pixel_refs_;
Picture::PixelRefIterator::PixelRefIterator()
: picture_(NULL),
current_pixel_refs_(empty_pixel_refs_.Pointer()),
current_index_(0),
min_point_(-1, -1),
max_point_(-1, -1),
current_x_(0),
current_y_(0) {
}
Picture::PixelRefIterator::PixelRefIterator(
const gfx::Rect& rect,
const Picture* picture)
: picture_(picture),
current_pixel_refs_(empty_pixel_refs_.Pointer()),
current_index_(0) {
gfx::Rect layer_rect = picture->layer_rect_;
gfx::Size cell_size = picture->cell_size_;
DCHECK(!cell_size.IsEmpty());
gfx::Rect query_rect(rect);
// Early out if the query rect doesn't intersect this picture.
if (!query_rect.Intersects(layer_rect)) {
min_point_ = gfx::Point(0, 0);
max_point_ = gfx::Point(0, 0);
current_x_ = 1;
current_y_ = 1;
return;
}
// First, subtract the layer origin as cells are stored in layer space.
query_rect.Offset(-layer_rect.OffsetFromOrigin());
// We have to find a cell_size aligned point that corresponds to
// query_rect. Point is a multiple of cell_size.
min_point_ = gfx::Point(
RoundDown(query_rect.x(), cell_size.width()),
RoundDown(query_rect.y(), cell_size.height()));
max_point_ = gfx::Point(
RoundDown(query_rect.right() - 1, cell_size.width()),
RoundDown(query_rect.bottom() - 1, cell_size.height()));
// Limit the points to known pixel ref boundaries.
min_point_ = gfx::Point(
std::max(min_point_.x(), picture->min_pixel_cell_.x()),
std::max(min_point_.y(), picture->min_pixel_cell_.y()));
max_point_ = gfx::Point(
std::min(max_point_.x(), picture->max_pixel_cell_.x()),
std::min(max_point_.y(), picture->max_pixel_cell_.y()));
// Make the current x be cell_size.width() less than min point, so that
// the first increment will point at min_point_.
current_x_ = min_point_.x() - cell_size.width();
current_y_ = min_point_.y();
if (current_y_ <= max_point_.y())
++(*this);
}
Picture::PixelRefIterator::~PixelRefIterator() {
}
Picture::PixelRefIterator& Picture::PixelRefIterator::operator++() {
++current_index_;
// If we're not at the end of the list, then we have the next item.
if (current_index_ < current_pixel_refs_->size())
return *this;
DCHECK(current_y_ <= max_point_.y());
while (true) {
gfx::Size cell_size = picture_->cell_size_;
// Advance the current grid cell.
current_x_ += cell_size.width();
if (current_x_ > max_point_.x()) {
current_y_ += cell_size.height();
current_x_ = min_point_.x();
if (current_y_ > max_point_.y()) {
current_pixel_refs_ = empty_pixel_refs_.Pointer();
current_index_ = 0;
break;
}
}
// If there are no pixel refs at this grid cell, keep incrementing.
PixelRefMapKey key(current_x_, current_y_);
PixelRefMap::const_iterator iter = picture_->pixel_refs_.find(key);
if (iter == picture_->pixel_refs_.end())
continue;
// We found a non-empty list: store it and get the first pixel ref.
current_pixel_refs_ = &iter->second;
current_index_ = 0;
break;
}
return *this;
}
scoped_refptr<base::debug::ConvertableToTraceFormat>
Picture::AsTraceableRasterData(float scale) const {
scoped_refptr<base::debug::TracedValue> raster_data =
new base::debug::TracedValue();
TracedValue::SetIDRef(this, raster_data.get(), "picture_id");
raster_data->SetDouble("scale", scale);
return raster_data;
}
scoped_refptr<base::debug::ConvertableToTraceFormat>
Picture::AsTraceableRecordData() const {
scoped_refptr<base::debug::TracedValue> record_data =
new base::debug::TracedValue();
TracedValue::SetIDRef(this, record_data.get(), "picture_id");
record_data->BeginArray("layer_rect");
MathUtil::AddToTracedValue(layer_rect_, record_data.get());
record_data->EndArray();
return record_data;
}
} // namespace cc