diff options
Diffstat (limited to 'webkit/glue/plugins/pepper_device_context_2d.cc')
-rw-r--r-- | webkit/glue/plugins/pepper_device_context_2d.cc | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/webkit/glue/plugins/pepper_device_context_2d.cc b/webkit/glue/plugins/pepper_device_context_2d.cc new file mode 100644 index 0000000..45ed9ee --- /dev/null +++ b/webkit/glue/plugins/pepper_device_context_2d.cc @@ -0,0 +1,553 @@ +// 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 NULL; + + scoped_refptr<DeviceContext2D> context(new DeviceContext2D(module)); + if (!context->Init(size->width, size->height, is_always_opaque)) + return NULL; + 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 |