| // 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/SkBBHFactory.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkData.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/geometry/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(); |
| } |
| |
| size_t Picture::ApproximateMemoryUsage() const { |
| DCHECK(picture_); |
| return SkPictureUtils::ApproximateBytesUsed(picture_.get()); |
| } |
| |
| 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()); |
| |
| // TODO(mtklein): If SkRTree sticks, clean up tile_grid_info. skbug.com/3085 |
| SkRTreeFactory factory; |
| SkPictureRecorder recorder; |
| |
| skia::RefPtr<SkCanvas> canvas; |
| canvas = skia::SharePtr(recorder.beginRecording( |
| layer_rect_.width(), layer_rect_.height(), &factory, |
| SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag)); |
| |
| 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; |
| 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_); |
| |
| 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 (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_); |
| 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; |
| 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.Pass(); |
| } |
| |
| 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 |