// 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 #include #include #include "base/base64.h" #include "base/debug/trace_event.h" #include "base/values.h" #include "cc/base/math_util.h" #include "cc/base/util.h" #include "cc/debug/rendering_stats.h" #include "cc/debug/traced_picture.h" #include "cc/debug/traced_value.h" #include "cc/layers/content_layer_client.h" #include "skia/ext/analysis_canvas.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/SkStream.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 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(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(buffer); // Try PNG first. if (gfx::PNGCodec::Decode(data, size, bm)) return true; // Try JPEG. scoped_ptr decoded_jpeg(gfx::JPEGCodec::Decode(data, size)); if (decoded_jpeg) { *bm = *decoded_jpeg; return true; } return false; } class DisableLCDTextFilter : public SkDrawFilter { public: // SkDrawFilter interface. virtual bool filter(SkPaint* paint, SkDrawFilter::Type type) OVERRIDE { if (type != SkDrawFilter::kText_Type) return true; paint->setLCDRenderText(false); return true; } }; // URI label for a lazily decoded SkPixelRef. const char kLabelLazyDecoded[] = "lazy"; void GatherPixelRefsForRect( SkPicture* picture, gfx::Rect rect, Picture::PixelRefs* pixel_refs) { DCHECK(picture); SkData* pixel_ref_data = SkPictureUtils::GatherPixelRefs( picture, gfx::RectToSkRect(rect)); if (!pixel_ref_data) return; void* data = const_cast(pixel_ref_data->data()); if (!data) { pixel_ref_data->unref(); return; } SkPixelRef** refs = reinterpret_cast(data); for (size_t i = 0; i < pixel_ref_data->size() / sizeof(*refs); ++i) { if (*refs && (*refs)->getURI() && !strncmp((*refs)->getURI(), kLabelLazyDecoded, 4)) { pixel_refs->push_back(static_cast(*refs)); } refs++; } pixel_ref_data->unref(); } } // namespace scoped_refptr Picture::Create(gfx::Rect layer_rect) { return make_scoped_refptr(new Picture(layer_rect)); } scoped_refptr Picture::CreateFromValue(const base::Value* value) { bool success; scoped_refptr picture = make_scoped_refptr(new Picture(value, &success)); if (!success) picture = NULL; return picture; } Picture::Picture(gfx::Rect layer_rect) : layer_rect_(layer_rect) { // Instead of recording a trace event for object creation here, we wait for // the picture to be recorded in Picture::Record. } Picture::Picture(const base::Value* raw_value, bool* success) { const base::DictionaryValue* value = NULL; if (!raw_value->GetAsDictionary(&value)) { *success = false; return; } // Decode the picture from base64. std::string encoded; if (!value->GetString("skp64", &encoded)) { *success = false; return; } std::string decoded; base::Base64Decode(encoded, &decoded); SkMemoryStream stream(decoded.data(), decoded.size()); const base::Value* layer_rect = NULL; if (!value->Get("params.layer_rect", &layer_rect)) { *success = false; return; } if (!MathUtil::FromValue(layer_rect, &layer_rect_)) { *success = false; return; } const base::Value* opaque_rect = NULL; if (!value->Get("params.opaque_rect", &opaque_rect)) { *success = false; return; } if (!MathUtil::FromValue(opaque_rect, &opaque_rect_)) { *success = false; return; } // Read the picture. This creates an empty picture on failure. picture_ = skia::AdoptRef(new SkPicture(&stream, success, &DecodeBitmap)); } Picture::Picture(const skia::RefPtr& picture, gfx::Rect layer_rect, gfx::Rect opaque_rect, const PixelRefMap& pixel_refs) : layer_rect_(layer_rect), opaque_rect_(opaque_rect), picture_(picture), pixel_refs_(pixel_refs) { } Picture::~Picture() { TRACE_EVENT_OBJECT_DELETED_WITH_ID( TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Picture", this); } scoped_refptr Picture::GetCloneForDrawingOnThread( unsigned thread_index) const { // SkPicture is not thread-safe to rasterize with, this returns a clone // to rasterize with on a specific thread. CHECK_GT(clones_.size(), thread_index); return clones_[thread_index]; } void Picture::CloneForDrawing(int num_threads) { TRACE_EVENT1("cc", "Picture::CloneForDrawing", "num_threads", num_threads); DCHECK(picture_); scoped_ptr clones(new SkPicture[num_threads]); picture_->clone(&clones[0], num_threads); clones_.clear(); for (int i = 0; i < num_threads; i++) { scoped_refptr clone = make_scoped_refptr( new Picture(skia::AdoptRef(new SkPicture(clones[i])), layer_rect_, opaque_rect_, pixel_refs_)); clones_.push_back(clone); TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Picture", clone.get(), TracedPicture::AsTraceablePicture(clone)); } } void Picture::Record(ContentLayerClient* painter, const SkTileGridPicture::TileGridInfo& tile_grid_info, RenderingStats* stats) { TRACE_EVENT2("cc", "Picture::Record", "width", layer_rect_.width(), "height", layer_rect_.height()); DCHECK(!tile_grid_info.fTileInterval.isEmpty()); picture_ = skia::AdoptRef(new SkTileGridPicture( layer_rect_.width(), layer_rect_.height(), tile_grid_info)); SkCanvas* canvas = picture_->beginRecording( layer_rect_.width(), layer_rect_.height(), SkPicture::kUsePathBoundsForClip_RecordingFlag | SkPicture::kOptimizeForClippedPlayback_RecordingFlag); 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); gfx::RectF opaque_layer_rect; base::TimeTicks begin_record_time; if (stats) begin_record_time = base::TimeTicks::Now(); painter->PaintContents(canvas, layer_rect_, &opaque_layer_rect); if (stats) { stats->total_record_time += base::TimeTicks::Now() - begin_record_time; stats->total_pixels_recorded += layer_rect_.width() * layer_rect_.height(); } canvas->restore(); picture_->endRecording(); opaque_rect_ = gfx::ToEnclosedRect(opaque_layer_rect); TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::Picture", this, TracedPicture::AsTraceablePicture(this)); } void Picture::GatherPixelRefs( const SkTileGridPicture::TileGridInfo& tile_grid_info, RenderingStats* stats) { TRACE_EVENT2("cc", "Picture::GatherPixelRefs", "width", layer_rect_.width(), "height", layer_rect_.height()); DCHECK(picture_); 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::max(); int min_y = std::numeric_limits::max(); int max_x = 0; int max_y = 0; base::TimeTicks begin_image_gathering_time; if (stats) begin_image_gathering_time = base::TimeTicks::Now(); gfx::Size layer_size(layer_rect_.size()); // Capture pixel refs for this picture in a grid // with cell_size_ sized cells. pixel_refs_.clear(); for (int y = 0; y < layer_rect_.height(); y += cell_size_.height()) { for (int x = 0; x < layer_rect_.width(); x += cell_size_.width()) { gfx::Rect rect(gfx::Point(x, y), cell_size_); rect.Intersect(gfx::Rect(gfx::Point(), layer_rect_.size())); PixelRefs pixel_refs; GatherPixelRefsForRect(picture_.get(), rect, &pixel_refs); // Only capture non-empty cells. if (!pixel_refs.empty()) { PixelRefMapKey key(x, y); pixel_refs_[key].swap(pixel_refs); min_x = std::min(min_x, x); min_y = std::min(min_y, y); max_x = std::max(max_x, x); max_y = std::max(max_y, y); } } } if (stats) { stats->total_image_gathering_time += base::TimeTicks::Now() - begin_image_gathering_time; stats->total_image_gathering_count++; } min_pixel_cell_ = gfx::Point(min_x, min_y); max_pixel_cell_ = gfx::Point(max_x, max_y); } void Picture::Raster( SkCanvas* canvas, SkDrawPictureCallback* callback, gfx::Rect content_rect, float contents_scale, bool enable_lcd_text) { TRACE_EVENT_BEGIN1("cc", "Picture::Raster", "data", AsTraceableRasterData(content_rect, contents_scale)); DCHECK(picture_); skia::RefPtr disable_lcd_text_filter; canvas->save(); canvas->clipRect(gfx::RectToSkRect(content_rect)); canvas->scale(contents_scale, contents_scale); canvas->translate(layer_rect_.x(), layer_rect_.y()); // Pictures by default have LCD text enabled. if (!enable_lcd_text) { disable_lcd_text_filter = skia::AdoptRef(new DisableLCDTextFilter); canvas->setDrawFilter(disable_lcd_text_filter.get()); } picture_->draw(canvas, callback); SkIRect bounds; canvas->getClipDeviceBounds(&bounds); canvas->restore(); TRACE_EVENT_END1("cc", "Picture::Raster", "num_pixels_rasterized", bounds.width() * bounds.height()); } scoped_ptr Picture::AsValue() const { SkDynamicMemoryWStream stream; // Serialize the picture. picture_->serialize(&stream, &EncodeBitmap); // Encode the picture as base64. scoped_ptr res(new base::DictionaryValue()); res->Set("params.layer_rect", MathUtil::AsValue(layer_rect_).release()); res->Set("params.opaque_rect", MathUtil::AsValue(opaque_rect_).release()); size_t serialized_size = stream.bytesWritten(); scoped_ptr 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::LazyInstance 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( gfx::Rect query_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_; // 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_ptr Picture::AsTraceableRasterData(gfx::Rect rect, float scale) { scoped_ptr raster_data(new base::DictionaryValue()); raster_data->Set("picture_id", TracedValue::CreateIDRef(this).release()); raster_data->SetDouble("scale", scale); raster_data->SetDouble("rect_x", rect.x()); raster_data->SetDouble("rect_y", rect.y()); raster_data->SetDouble("rect_width", rect.width()); raster_data->SetDouble("rect_height", rect.height()); return TracedValue::FromValue(raster_data.release()); } } // namespace cc