| Index: webkit/glue/plugins/pepper_device_context_2d.cc
|
| ===================================================================
|
| --- webkit/glue/plugins/pepper_device_context_2d.cc (revision 57791)
|
| +++ webkit/glue/plugins/pepper_device_context_2d.cc (working copy)
|
| @@ -1,553 +0,0 @@
|
| -// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_device_context_2d.h"
|
| -
|
| -#include <iterator>
|
| -
|
| -#include "base/logging.h"
|
| -#include "base/message_loop.h"
|
| -#include "base/task.h"
|
| -#include "gfx/blit.h"
|
| -#include "gfx/point.h"
|
| -#include "gfx/rect.h"
|
| -#include "skia/ext/platform_canvas.h"
|
| -#include "third_party/ppapi/c/pp_errors.h"
|
| -#include "third_party/ppapi/c/pp_module.h"
|
| -#include "third_party/ppapi/c/pp_rect.h"
|
| -#include "third_party/ppapi/c/pp_resource.h"
|
| -#include "third_party/ppapi/c/ppb_device_context_2d.h"
|
| -#include "third_party/skia/include/core/SkBitmap.h"
|
| -#include "webkit/glue/plugins/pepper_image_data.h"
|
| -#include "webkit/glue/plugins/pepper_plugin_instance.h"
|
| -#include "webkit/glue/plugins/pepper_plugin_module.h"
|
| -
|
| -#if defined(OS_MACOSX)
|
| -#include "base/mac_util.h"
|
| -#include "base/scoped_cftyperef.h"
|
| -#endif
|
| -
|
| -namespace pepper {
|
| -
|
| -namespace {
|
| -
|
| -// Converts a rect inside an image of the given dimensions. The rect may be
|
| -// NULL to indicate it should be the entire image. If the rect is outside of
|
| -// the image, this will do nothing and return false.
|
| -bool ValidateAndConvertRect(const PP_Rect* rect,
|
| - int image_width, int image_height,
|
| - gfx::Rect* dest) {
|
| - if (!rect) {
|
| - // Use the entire image area.
|
| - *dest = gfx::Rect(0, 0, image_width, image_height);
|
| - } else {
|
| - // Validate the passed-in area.
|
| - if (rect->point.x < 0 || rect->point.y < 0 ||
|
| - rect->size.width <= 0 || rect->size.height <= 0)
|
| - return false;
|
| -
|
| - // Check the max bounds, being careful of overflow.
|
| - if (static_cast<int64>(rect->point.x) +
|
| - static_cast<int64>(rect->size.width) >
|
| - static_cast<int64>(image_width))
|
| - return false;
|
| - if (static_cast<int64>(rect->point.y) +
|
| - static_cast<int64>(rect->size.height) >
|
| - static_cast<int64>(image_height))
|
| - return false;
|
| -
|
| - *dest = gfx::Rect(rect->point.x, rect->point.y,
|
| - rect->size.width, rect->size.height);
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -PP_Resource Create(PP_Module module_id,
|
| - const PP_Size* size,
|
| - bool is_always_opaque) {
|
| - PluginModule* module = PluginModule::FromPPModule(module_id);
|
| - if (!module)
|
| - return 0;
|
| -
|
| - scoped_refptr<DeviceContext2D> context(new DeviceContext2D(module));
|
| - if (!context->Init(size->width, size->height, is_always_opaque))
|
| - return 0;
|
| - return context->GetReference();
|
| -}
|
| -
|
| -bool IsDeviceContext2D(PP_Resource resource) {
|
| - return !!Resource::GetAs<DeviceContext2D>(resource);
|
| -}
|
| -
|
| -bool Describe(PP_Resource device_context,
|
| - PP_Size* size,
|
| - bool* is_always_opaque) {
|
| - scoped_refptr<DeviceContext2D> context(
|
| - Resource::GetAs<DeviceContext2D>(device_context));
|
| - if (!context)
|
| - return false;
|
| - return context->Describe(size, is_always_opaque);
|
| -}
|
| -
|
| -bool PaintImageData(PP_Resource device_context,
|
| - PP_Resource image,
|
| - const PP_Point* top_left,
|
| - const PP_Rect* src_rect) {
|
| - scoped_refptr<DeviceContext2D> context(
|
| - Resource::GetAs<DeviceContext2D>(device_context));
|
| - if (!context)
|
| - return false;
|
| - return context->PaintImageData(image, top_left, src_rect);
|
| -}
|
| -
|
| -bool Scroll(PP_Resource device_context,
|
| - const PP_Rect* clip_rect,
|
| - const PP_Point* amount) {
|
| - scoped_refptr<DeviceContext2D> context(
|
| - Resource::GetAs<DeviceContext2D>(device_context));
|
| - if (!context)
|
| - return false;
|
| - return context->Scroll(clip_rect, amount);
|
| -}
|
| -
|
| -bool ReplaceContents(PP_Resource device_context, PP_Resource image) {
|
| - scoped_refptr<DeviceContext2D> context(
|
| - Resource::GetAs<DeviceContext2D>(device_context));
|
| - if (!context)
|
| - return false;
|
| - return context->ReplaceContents(image);
|
| -}
|
| -
|
| -int32_t Flush(PP_Resource device_context,
|
| - PP_CompletionCallback callback) {
|
| - scoped_refptr<DeviceContext2D> context(
|
| - Resource::GetAs<DeviceContext2D>(device_context));
|
| - if (!context)
|
| - return PP_ERROR_BADRESOURCE;
|
| - return context->Flush(callback);
|
| -}
|
| -
|
| -const PPB_DeviceContext2D ppb_devicecontext2d = {
|
| - &Create,
|
| - &IsDeviceContext2D,
|
| - &Describe,
|
| - &PaintImageData,
|
| - &Scroll,
|
| - &ReplaceContents,
|
| - &Flush
|
| -};
|
| -
|
| -} // namespace
|
| -
|
| -struct DeviceContext2D::QueuedOperation {
|
| - enum Type {
|
| - PAINT,
|
| - SCROLL,
|
| - REPLACE
|
| - };
|
| -
|
| - QueuedOperation(Type t)
|
| - : type(t),
|
| - paint_x(0),
|
| - paint_y(0),
|
| - scroll_dx(0),
|
| - scroll_dy(0) {
|
| - }
|
| -
|
| - Type type;
|
| -
|
| - // Valid when type == PAINT.
|
| - scoped_refptr<ImageData> paint_image;
|
| - int paint_x, paint_y;
|
| - gfx::Rect paint_src_rect;
|
| -
|
| - // Valid when type == SCROLL.
|
| - gfx::Rect scroll_clip_rect;
|
| - int scroll_dx, scroll_dy;
|
| -
|
| - // Valid when type == REPLACE.
|
| - scoped_refptr<ImageData> replace_image;
|
| -};
|
| -
|
| -DeviceContext2D::DeviceContext2D(PluginModule* module)
|
| - : Resource(module),
|
| - bound_instance_(NULL),
|
| - flushed_any_data_(false),
|
| - offscreen_flush_pending_(false) {
|
| -}
|
| -
|
| -DeviceContext2D::~DeviceContext2D() {
|
| -}
|
| -
|
| -// static
|
| -const PPB_DeviceContext2D* DeviceContext2D::GetInterface() {
|
| - return &ppb_devicecontext2d;
|
| -}
|
| -
|
| -bool DeviceContext2D::Init(int width, int height, bool is_always_opaque) {
|
| - // The underlying ImageData will validate the dimensions.
|
| - image_data_ = new ImageData(module());
|
| - if (!image_data_->Init(PP_IMAGEDATAFORMAT_BGRA_PREMUL, width, height, true) ||
|
| - !image_data_->Map()) {
|
| - image_data_ = NULL;
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool DeviceContext2D::Describe(PP_Size* size, bool* is_always_opaque) {
|
| - size->width = image_data_->width();
|
| - size->height = image_data_->height();
|
| - *is_always_opaque = false; // TODO(brettw) implement this.
|
| - return true;
|
| -}
|
| -
|
| -bool DeviceContext2D::PaintImageData(PP_Resource image,
|
| - const PP_Point* top_left,
|
| - const PP_Rect* src_rect) {
|
| - if (!top_left)
|
| - return false;
|
| -
|
| - scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image));
|
| - if (!image_resource)
|
| - return false;
|
| -
|
| - QueuedOperation operation(QueuedOperation::PAINT);
|
| - operation.paint_image = image_resource;
|
| - if (!ValidateAndConvertRect(src_rect, image_resource->width(),
|
| - image_resource->height(),
|
| - &operation.paint_src_rect))
|
| - return false;
|
| -
|
| - // Validate the bitmap position using the previously-validated rect, there
|
| - // should be no painted area outside of the image.
|
| - int64 x64 = static_cast<int64>(top_left->x);
|
| - int64 y64 = static_cast<int64>(top_left->y);
|
| - if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 ||
|
| - x64 + static_cast<int64>(operation.paint_src_rect.right()) >
|
| - image_data_->width())
|
| - return false;
|
| - if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 ||
|
| - y64 + static_cast<int64>(operation.paint_src_rect.bottom()) >
|
| - image_data_->height())
|
| - return false;
|
| - operation.paint_x = top_left->x;
|
| - operation.paint_y = top_left->y;
|
| -
|
| - queued_operations_.push_back(operation);
|
| - return true;
|
| -}
|
| -
|
| -bool DeviceContext2D::Scroll(const PP_Rect* clip_rect,
|
| - const PP_Point* amount) {
|
| - QueuedOperation operation(QueuedOperation::SCROLL);
|
| - if (!ValidateAndConvertRect(clip_rect,
|
| - image_data_->width(),
|
| - image_data_->height(),
|
| - &operation.scroll_clip_rect))
|
| - return false;
|
| -
|
| - // If we're being asked to scroll by more than the clip rect size, just
|
| - // ignore this scroll command and say it worked.
|
| - int32 dx = amount->x;
|
| - int32 dy = amount->y;
|
| - if (dx <= -image_data_->width() || dx >= image_data_->width() ||
|
| - dx <= -image_data_->height() || dy >= image_data_->height())
|
| - return true;
|
| -
|
| - operation.scroll_dx = dx;
|
| - operation.scroll_dy = dy;
|
| -
|
| - queued_operations_.push_back(operation);
|
| - return false;
|
| -}
|
| -
|
| -bool DeviceContext2D::ReplaceContents(PP_Resource image) {
|
| - scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image));
|
| - if (!image_resource)
|
| - return false;
|
| - if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL)
|
| - return false;
|
| -
|
| - if (image_resource->width() != image_data_->width() ||
|
| - image_resource->height() != image_data_->height())
|
| - return false;
|
| -
|
| - QueuedOperation operation(QueuedOperation::REPLACE);
|
| - operation.replace_image = image_resource;
|
| - queued_operations_.push_back(operation);
|
| -
|
| - return true;
|
| -}
|
| -
|
| -int32_t DeviceContext2D::Flush(const PP_CompletionCallback& callback) {
|
| - // Don't allow more than one pending flush at a time.
|
| - if (HasPendingFlush())
|
| - return PP_ERROR_INPROGRESS;
|
| -
|
| - // TODO(brettw) check that the current thread is not the main one and
|
| - // implement blocking flushes in this case.
|
| - if (!callback.func)
|
| - return PP_ERROR_BADARGUMENT;
|
| -
|
| - gfx::Rect changed_rect;
|
| - for (size_t i = 0; i < queued_operations_.size(); i++) {
|
| - QueuedOperation& operation = queued_operations_[i];
|
| - gfx::Rect op_rect;
|
| - switch (operation.type) {
|
| - case QueuedOperation::PAINT:
|
| - ExecutePaintImageData(operation.paint_image,
|
| - operation.paint_x, operation.paint_y,
|
| - operation.paint_src_rect,
|
| - &op_rect);
|
| - break;
|
| - case QueuedOperation::SCROLL:
|
| - ExecuteScroll(operation.scroll_clip_rect,
|
| - operation.scroll_dx, operation.scroll_dy,
|
| - &op_rect);
|
| - break;
|
| - case QueuedOperation::REPLACE:
|
| - ExecuteReplaceContents(operation.replace_image, &op_rect);
|
| - break;
|
| - }
|
| - changed_rect = changed_rect.Union(op_rect);
|
| - }
|
| - queued_operations_.clear();
|
| - flushed_any_data_ = true;
|
| -
|
| - // We need the rect to be in terms of the current clip rect of the plugin
|
| - // since that's what will actually be painted. If we issue an invalidate
|
| - // for a clipped-out region, WebKit will do nothing and we won't get any
|
| - // ViewInitiatedPaint/ViewFlushedPaint calls, leaving our callback stranded.
|
| - gfx::Rect visible_changed_rect;
|
| - if (bound_instance_ && !changed_rect.IsEmpty())
|
| - visible_changed_rect = bound_instance_->clip().Intersect(changed_rect);
|
| -
|
| - if (bound_instance_ && !visible_changed_rect.IsEmpty()) {
|
| - unpainted_flush_callback_.Set(callback);
|
| - bound_instance_->InvalidateRect(visible_changed_rect);
|
| - } else {
|
| - // There's nothing visible to invalidate so just schedule the callback to
|
| - // execute in the next round of the message loop.
|
| - ScheduleOffscreenCallback(FlushCallbackData(callback));
|
| - }
|
| - return PP_ERROR_WOULDBLOCK;
|
| -}
|
| -
|
| -bool DeviceContext2D::ReadImageData(PP_Resource image,
|
| - const PP_Point* top_left) {
|
| - // Get and validate the image object to paint into.
|
| - scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image));
|
| - if (!image_resource)
|
| - return false;
|
| - if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL)
|
| - return false; // Must be in the right format.
|
| -
|
| - // Validate the bitmap position.
|
| - int x = top_left->x;
|
| - if (x < 0 ||
|
| - static_cast<int64>(x) + static_cast<int64>(image_resource->width()) >
|
| - image_data_->width())
|
| - return false;
|
| - int y = top_left->y;
|
| - if (y < 0 ||
|
| - static_cast<int64>(y) + static_cast<int64>(image_resource->height()) >
|
| - image_data_->height())
|
| - return false;
|
| -
|
| - ImageDataAutoMapper auto_mapper(image_resource);
|
| - if (!auto_mapper.is_valid())
|
| - return false;
|
| - skia::PlatformCanvas* dest_canvas = image_resource->mapped_canvas();
|
| -
|
| - SkIRect src_irect = { x, y,
|
| - x + image_resource->width(),
|
| - y + image_resource->height() };
|
| - SkRect dest_rect = { SkIntToScalar(0),
|
| - SkIntToScalar(0),
|
| - SkIntToScalar(image_resource->width()),
|
| - SkIntToScalar(image_resource->height()) };
|
| -
|
| - // We want to replace the contents of the bitmap rather than blend.
|
| - SkPaint paint;
|
| - paint.setXfermodeMode(SkXfermode::kSrc_Mode);
|
| - dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(),
|
| - &src_irect, dest_rect, &paint);
|
| - return true;
|
| -}
|
| -
|
| -bool DeviceContext2D::BindToInstance(PluginInstance* new_instance) {
|
| - if (bound_instance_ == new_instance)
|
| - return true; // Rebinding the same device, nothing to do.
|
| - if (bound_instance_ && new_instance)
|
| - return false; // Can't change a bound device.
|
| -
|
| - if (!new_instance) {
|
| - // When the device is detached, we'll not get any more paint callbacks so
|
| - // we need to clear the list, but we still want to issue any pending
|
| - // callbacks to the plugin.
|
| - if (!unpainted_flush_callback_.is_null()) {
|
| - ScheduleOffscreenCallback(unpainted_flush_callback_);
|
| - unpainted_flush_callback_.Clear();
|
| - }
|
| - if (!painted_flush_callback_.is_null()) {
|
| - ScheduleOffscreenCallback(painted_flush_callback_);
|
| - painted_flush_callback_.Clear();
|
| - }
|
| - } else if (flushed_any_data_) {
|
| - // Only schedule a paint if this backing store has had any data flushed to
|
| - // it. This is an optimization. A "normal" plugin will first allocated a
|
| - // backing store, bind it, and then execute their normal painting and
|
| - // update loop. If binding a device always invalidated, it would mean we
|
| - // would get one paint for the bind, and one for the first time the plugin
|
| - // actually painted something. By not bothering to schedule an invalidate
|
| - // when an empty device is initially bound, we can save an extra paint for
|
| - // many plugins during the critical page initialization phase.
|
| - new_instance->InvalidateRect(gfx::Rect());
|
| - }
|
| -
|
| - bound_instance_ = new_instance;
|
| - return true;
|
| -}
|
| -
|
| -void DeviceContext2D::Paint(WebKit::WebCanvas* canvas,
|
| - const gfx::Rect& plugin_rect,
|
| - const gfx::Rect& paint_rect) {
|
| - // We're guaranteed to have a mapped canvas since we mapped it in Init().
|
| - const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap();
|
| -
|
| -#if defined(OS_MACOSX)
|
| - SkAutoLockPixels lock(backing_bitmap);
|
| -
|
| - scoped_cftyperef<CGDataProviderRef> data_provider(
|
| - CGDataProviderCreateWithData(
|
| - NULL, backing_bitmap.getAddr32(0, 0),
|
| - backing_bitmap.rowBytes() * backing_bitmap.height(), NULL));
|
| - scoped_cftyperef<CGImageRef> image(
|
| - CGImageCreate(
|
| - backing_bitmap.width(), backing_bitmap.height(),
|
| - 8, 32, backing_bitmap.rowBytes(),
|
| - mac_util::GetSystemColorSpace(),
|
| - kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
|
| - data_provider, NULL, false, kCGRenderingIntentDefault));
|
| -
|
| - // Flip the transform
|
| - CGContextSaveGState(canvas);
|
| - float window_height = static_cast<float>(CGBitmapContextGetHeight(canvas));
|
| - CGContextTranslateCTM(canvas, 0, window_height);
|
| - CGContextScaleCTM(canvas, 1.0, -1.0);
|
| -
|
| - CGRect bounds;
|
| - bounds.origin.x = plugin_rect.origin().x();
|
| - bounds.origin.y = window_height - plugin_rect.origin().y() -
|
| - backing_bitmap.height();
|
| - bounds.size.width = backing_bitmap.width();
|
| - bounds.size.height = backing_bitmap.height();
|
| -
|
| - CGContextDrawImage(canvas, bounds, image);
|
| - CGContextRestoreGState(canvas);
|
| -#else
|
| - gfx::Point origin(plugin_rect.origin().x(), plugin_rect.origin().y());
|
| - canvas->drawBitmap(backing_bitmap,
|
| - SkIntToScalar(plugin_rect.origin().x()),
|
| - SkIntToScalar(plugin_rect.origin().y()));
|
| -#endif
|
| -}
|
| -
|
| -void DeviceContext2D::ViewInitiatedPaint() {
|
| - // Move any "unpainted" callback to the painted state. See
|
| - // |unpainted_flush_callback_| in the header for more.
|
| - if (!unpainted_flush_callback_.is_null()) {
|
| - DCHECK(painted_flush_callback_.is_null());
|
| - std::swap(painted_flush_callback_, unpainted_flush_callback_);
|
| - }
|
| -}
|
| -
|
| -void DeviceContext2D::ViewFlushedPaint() {
|
| - // Notify any "painted" callback. See |unpainted_flush_callback_| in the
|
| - // header for more.
|
| - if (!painted_flush_callback_.is_null()) {
|
| - // We must clear this variable before issuing the callback. It will be
|
| - // common for the plugin to issue another invalidate in response to a flush
|
| - // callback, and we don't want to think that a callback is already pending.
|
| - FlushCallbackData callback;
|
| - std::swap(callback, painted_flush_callback_);
|
| - callback.Execute(PP_OK);
|
| - }
|
| -}
|
| -
|
| -void DeviceContext2D::ExecutePaintImageData(ImageData* image,
|
| - int x, int y,
|
| - const gfx::Rect& src_rect,
|
| - gfx::Rect* invalidated_rect) {
|
| - // Ensure the source image is mapped to read from it.
|
| - ImageDataAutoMapper auto_mapper(image);
|
| - if (!auto_mapper.is_valid())
|
| - return;
|
| -
|
| - // Portion within the source image to cut out.
|
| - SkIRect src_irect = { src_rect.x(), src_rect.y(),
|
| - src_rect.right(), src_rect.bottom() };
|
| -
|
| - // Location within the backing store to copy to.
|
| - *invalidated_rect = src_rect;
|
| - invalidated_rect->Offset(x, y);
|
| - SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()),
|
| - SkIntToScalar(invalidated_rect->y()),
|
| - SkIntToScalar(invalidated_rect->right()),
|
| - SkIntToScalar(invalidated_rect->bottom()) };
|
| -
|
| - // We're guaranteed to have a mapped canvas since we mapped it in Init().
|
| - skia::PlatformCanvas* backing_canvas = image_data_->mapped_canvas();
|
| -
|
| - // We want to replace the contents of the bitmap rather than blend.
|
| - SkPaint paint;
|
| - paint.setXfermodeMode(SkXfermode::kSrc_Mode);
|
| - backing_canvas->drawBitmapRect(*image->GetMappedBitmap(),
|
| - &src_irect, dest_rect, &paint);
|
| -}
|
| -
|
| -void DeviceContext2D::ExecuteScroll(const gfx::Rect& clip, int dx, int dy,
|
| - gfx::Rect* invalidated_rect) {
|
| - gfx::ScrollCanvas(image_data_->mapped_canvas(),
|
| - clip, gfx::Point(dx, dy));
|
| - *invalidated_rect = clip;
|
| -}
|
| -
|
| -void DeviceContext2D::ExecuteReplaceContents(ImageData* image,
|
| - gfx::Rect* invalidated_rect) {
|
| - image_data_->Swap(image);
|
| - *invalidated_rect = gfx::Rect(0, 0,
|
| - image_data_->width(), image_data_->height());
|
| -}
|
| -
|
| -void DeviceContext2D::ScheduleOffscreenCallback(
|
| - const FlushCallbackData& callback) {
|
| - DCHECK(!HasPendingFlush());
|
| - offscreen_flush_pending_ = true;
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE,
|
| - NewRunnableMethod(this,
|
| - &DeviceContext2D::ExecuteOffscreenCallback,
|
| - callback));
|
| -}
|
| -
|
| -void DeviceContext2D::ExecuteOffscreenCallback(FlushCallbackData data) {
|
| - DCHECK(offscreen_flush_pending_);
|
| -
|
| - // We must clear this flag before issuing the callback. It will be
|
| - // common for the plugin to issue another invalidate in response to a flush
|
| - // callback, and we don't want to think that a callback is already pending.
|
| - offscreen_flush_pending_ = false;
|
| - data.Execute(PP_OK);
|
| -}
|
| -
|
| -bool DeviceContext2D::HasPendingFlush() const {
|
| - return !unpainted_flush_callback_.is_null() ||
|
| - !painted_flush_callback_.is_null() ||
|
| - offscreen_flush_pending_;
|
| -}
|
| -
|
| -} // namespace pepper
|
|
|