OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "webkit/glue/plugins/pepper_device_context_2d.h" | |
6 | |
7 #include <iterator> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/message_loop.h" | |
11 #include "base/task.h" | |
12 #include "gfx/blit.h" | |
13 #include "gfx/point.h" | |
14 #include "gfx/rect.h" | |
15 #include "skia/ext/platform_canvas.h" | |
16 #include "third_party/ppapi/c/pp_errors.h" | |
17 #include "third_party/ppapi/c/pp_module.h" | |
18 #include "third_party/ppapi/c/pp_rect.h" | |
19 #include "third_party/ppapi/c/pp_resource.h" | |
20 #include "third_party/ppapi/c/ppb_device_context_2d.h" | |
21 #include "third_party/skia/include/core/SkBitmap.h" | |
22 #include "webkit/glue/plugins/pepper_image_data.h" | |
23 #include "webkit/glue/plugins/pepper_plugin_instance.h" | |
24 #include "webkit/glue/plugins/pepper_plugin_module.h" | |
25 | |
26 #if defined(OS_MACOSX) | |
27 #include "base/mac_util.h" | |
28 #include "base/scoped_cftyperef.h" | |
29 #endif | |
30 | |
31 namespace pepper { | |
32 | |
33 namespace { | |
34 | |
35 // Converts a rect inside an image of the given dimensions. The rect may be | |
36 // NULL to indicate it should be the entire image. If the rect is outside of | |
37 // the image, this will do nothing and return false. | |
38 bool ValidateAndConvertRect(const PP_Rect* rect, | |
39 int image_width, int image_height, | |
40 gfx::Rect* dest) { | |
41 if (!rect) { | |
42 // Use the entire image area. | |
43 *dest = gfx::Rect(0, 0, image_width, image_height); | |
44 } else { | |
45 // Validate the passed-in area. | |
46 if (rect->point.x < 0 || rect->point.y < 0 || | |
47 rect->size.width <= 0 || rect->size.height <= 0) | |
48 return false; | |
49 | |
50 // Check the max bounds, being careful of overflow. | |
51 if (static_cast<int64>(rect->point.x) + | |
52 static_cast<int64>(rect->size.width) > | |
53 static_cast<int64>(image_width)) | |
54 return false; | |
55 if (static_cast<int64>(rect->point.y) + | |
56 static_cast<int64>(rect->size.height) > | |
57 static_cast<int64>(image_height)) | |
58 return false; | |
59 | |
60 *dest = gfx::Rect(rect->point.x, rect->point.y, | |
61 rect->size.width, rect->size.height); | |
62 } | |
63 return true; | |
64 } | |
65 | |
66 PP_Resource Create(PP_Module module_id, | |
67 const PP_Size* size, | |
68 bool is_always_opaque) { | |
69 PluginModule* module = PluginModule::FromPPModule(module_id); | |
70 if (!module) | |
71 return 0; | |
72 | |
73 scoped_refptr<DeviceContext2D> context(new DeviceContext2D(module)); | |
74 if (!context->Init(size->width, size->height, is_always_opaque)) | |
75 return 0; | |
76 return context->GetReference(); | |
77 } | |
78 | |
79 bool IsDeviceContext2D(PP_Resource resource) { | |
80 return !!Resource::GetAs<DeviceContext2D>(resource); | |
81 } | |
82 | |
83 bool Describe(PP_Resource device_context, | |
84 PP_Size* size, | |
85 bool* is_always_opaque) { | |
86 scoped_refptr<DeviceContext2D> context( | |
87 Resource::GetAs<DeviceContext2D>(device_context)); | |
88 if (!context) | |
89 return false; | |
90 return context->Describe(size, is_always_opaque); | |
91 } | |
92 | |
93 bool PaintImageData(PP_Resource device_context, | |
94 PP_Resource image, | |
95 const PP_Point* top_left, | |
96 const PP_Rect* src_rect) { | |
97 scoped_refptr<DeviceContext2D> context( | |
98 Resource::GetAs<DeviceContext2D>(device_context)); | |
99 if (!context) | |
100 return false; | |
101 return context->PaintImageData(image, top_left, src_rect); | |
102 } | |
103 | |
104 bool Scroll(PP_Resource device_context, | |
105 const PP_Rect* clip_rect, | |
106 const PP_Point* amount) { | |
107 scoped_refptr<DeviceContext2D> context( | |
108 Resource::GetAs<DeviceContext2D>(device_context)); | |
109 if (!context) | |
110 return false; | |
111 return context->Scroll(clip_rect, amount); | |
112 } | |
113 | |
114 bool ReplaceContents(PP_Resource device_context, PP_Resource image) { | |
115 scoped_refptr<DeviceContext2D> context( | |
116 Resource::GetAs<DeviceContext2D>(device_context)); | |
117 if (!context) | |
118 return false; | |
119 return context->ReplaceContents(image); | |
120 } | |
121 | |
122 int32_t Flush(PP_Resource device_context, | |
123 PP_CompletionCallback callback) { | |
124 scoped_refptr<DeviceContext2D> context( | |
125 Resource::GetAs<DeviceContext2D>(device_context)); | |
126 if (!context) | |
127 return PP_ERROR_BADRESOURCE; | |
128 return context->Flush(callback); | |
129 } | |
130 | |
131 const PPB_DeviceContext2D ppb_devicecontext2d = { | |
132 &Create, | |
133 &IsDeviceContext2D, | |
134 &Describe, | |
135 &PaintImageData, | |
136 &Scroll, | |
137 &ReplaceContents, | |
138 &Flush | |
139 }; | |
140 | |
141 } // namespace | |
142 | |
143 struct DeviceContext2D::QueuedOperation { | |
144 enum Type { | |
145 PAINT, | |
146 SCROLL, | |
147 REPLACE | |
148 }; | |
149 | |
150 QueuedOperation(Type t) | |
151 : type(t), | |
152 paint_x(0), | |
153 paint_y(0), | |
154 scroll_dx(0), | |
155 scroll_dy(0) { | |
156 } | |
157 | |
158 Type type; | |
159 | |
160 // Valid when type == PAINT. | |
161 scoped_refptr<ImageData> paint_image; | |
162 int paint_x, paint_y; | |
163 gfx::Rect paint_src_rect; | |
164 | |
165 // Valid when type == SCROLL. | |
166 gfx::Rect scroll_clip_rect; | |
167 int scroll_dx, scroll_dy; | |
168 | |
169 // Valid when type == REPLACE. | |
170 scoped_refptr<ImageData> replace_image; | |
171 }; | |
172 | |
173 DeviceContext2D::DeviceContext2D(PluginModule* module) | |
174 : Resource(module), | |
175 bound_instance_(NULL), | |
176 flushed_any_data_(false), | |
177 offscreen_flush_pending_(false) { | |
178 } | |
179 | |
180 DeviceContext2D::~DeviceContext2D() { | |
181 } | |
182 | |
183 // static | |
184 const PPB_DeviceContext2D* DeviceContext2D::GetInterface() { | |
185 return &ppb_devicecontext2d; | |
186 } | |
187 | |
188 bool DeviceContext2D::Init(int width, int height, bool is_always_opaque) { | |
189 // The underlying ImageData will validate the dimensions. | |
190 image_data_ = new ImageData(module()); | |
191 if (!image_data_->Init(PP_IMAGEDATAFORMAT_BGRA_PREMUL, width, height, true) || | |
192 !image_data_->Map()) { | |
193 image_data_ = NULL; | |
194 return false; | |
195 } | |
196 | |
197 return true; | |
198 } | |
199 | |
200 bool DeviceContext2D::Describe(PP_Size* size, bool* is_always_opaque) { | |
201 size->width = image_data_->width(); | |
202 size->height = image_data_->height(); | |
203 *is_always_opaque = false; // TODO(brettw) implement this. | |
204 return true; | |
205 } | |
206 | |
207 bool DeviceContext2D::PaintImageData(PP_Resource image, | |
208 const PP_Point* top_left, | |
209 const PP_Rect* src_rect) { | |
210 if (!top_left) | |
211 return false; | |
212 | |
213 scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); | |
214 if (!image_resource) | |
215 return false; | |
216 | |
217 QueuedOperation operation(QueuedOperation::PAINT); | |
218 operation.paint_image = image_resource; | |
219 if (!ValidateAndConvertRect(src_rect, image_resource->width(), | |
220 image_resource->height(), | |
221 &operation.paint_src_rect)) | |
222 return false; | |
223 | |
224 // Validate the bitmap position using the previously-validated rect, there | |
225 // should be no painted area outside of the image. | |
226 int64 x64 = static_cast<int64>(top_left->x); | |
227 int64 y64 = static_cast<int64>(top_left->y); | |
228 if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 || | |
229 x64 + static_cast<int64>(operation.paint_src_rect.right()) > | |
230 image_data_->width()) | |
231 return false; | |
232 if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 || | |
233 y64 + static_cast<int64>(operation.paint_src_rect.bottom()) > | |
234 image_data_->height()) | |
235 return false; | |
236 operation.paint_x = top_left->x; | |
237 operation.paint_y = top_left->y; | |
238 | |
239 queued_operations_.push_back(operation); | |
240 return true; | |
241 } | |
242 | |
243 bool DeviceContext2D::Scroll(const PP_Rect* clip_rect, | |
244 const PP_Point* amount) { | |
245 QueuedOperation operation(QueuedOperation::SCROLL); | |
246 if (!ValidateAndConvertRect(clip_rect, | |
247 image_data_->width(), | |
248 image_data_->height(), | |
249 &operation.scroll_clip_rect)) | |
250 return false; | |
251 | |
252 // If we're being asked to scroll by more than the clip rect size, just | |
253 // ignore this scroll command and say it worked. | |
254 int32 dx = amount->x; | |
255 int32 dy = amount->y; | |
256 if (dx <= -image_data_->width() || dx >= image_data_->width() || | |
257 dx <= -image_data_->height() || dy >= image_data_->height()) | |
258 return true; | |
259 | |
260 operation.scroll_dx = dx; | |
261 operation.scroll_dy = dy; | |
262 | |
263 queued_operations_.push_back(operation); | |
264 return false; | |
265 } | |
266 | |
267 bool DeviceContext2D::ReplaceContents(PP_Resource image) { | |
268 scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); | |
269 if (!image_resource) | |
270 return false; | |
271 if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL) | |
272 return false; | |
273 | |
274 if (image_resource->width() != image_data_->width() || | |
275 image_resource->height() != image_data_->height()) | |
276 return false; | |
277 | |
278 QueuedOperation operation(QueuedOperation::REPLACE); | |
279 operation.replace_image = image_resource; | |
280 queued_operations_.push_back(operation); | |
281 | |
282 return true; | |
283 } | |
284 | |
285 int32_t DeviceContext2D::Flush(const PP_CompletionCallback& callback) { | |
286 // Don't allow more than one pending flush at a time. | |
287 if (HasPendingFlush()) | |
288 return PP_ERROR_INPROGRESS; | |
289 | |
290 // TODO(brettw) check that the current thread is not the main one and | |
291 // implement blocking flushes in this case. | |
292 if (!callback.func) | |
293 return PP_ERROR_BADARGUMENT; | |
294 | |
295 gfx::Rect changed_rect; | |
296 for (size_t i = 0; i < queued_operations_.size(); i++) { | |
297 QueuedOperation& operation = queued_operations_[i]; | |
298 gfx::Rect op_rect; | |
299 switch (operation.type) { | |
300 case QueuedOperation::PAINT: | |
301 ExecutePaintImageData(operation.paint_image, | |
302 operation.paint_x, operation.paint_y, | |
303 operation.paint_src_rect, | |
304 &op_rect); | |
305 break; | |
306 case QueuedOperation::SCROLL: | |
307 ExecuteScroll(operation.scroll_clip_rect, | |
308 operation.scroll_dx, operation.scroll_dy, | |
309 &op_rect); | |
310 break; | |
311 case QueuedOperation::REPLACE: | |
312 ExecuteReplaceContents(operation.replace_image, &op_rect); | |
313 break; | |
314 } | |
315 changed_rect = changed_rect.Union(op_rect); | |
316 } | |
317 queued_operations_.clear(); | |
318 flushed_any_data_ = true; | |
319 | |
320 // We need the rect to be in terms of the current clip rect of the plugin | |
321 // since that's what will actually be painted. If we issue an invalidate | |
322 // for a clipped-out region, WebKit will do nothing and we won't get any | |
323 // ViewInitiatedPaint/ViewFlushedPaint calls, leaving our callback stranded. | |
324 gfx::Rect visible_changed_rect; | |
325 if (bound_instance_ && !changed_rect.IsEmpty()) | |
326 visible_changed_rect = bound_instance_->clip().Intersect(changed_rect); | |
327 | |
328 if (bound_instance_ && !visible_changed_rect.IsEmpty()) { | |
329 unpainted_flush_callback_.Set(callback); | |
330 bound_instance_->InvalidateRect(visible_changed_rect); | |
331 } else { | |
332 // There's nothing visible to invalidate so just schedule the callback to | |
333 // execute in the next round of the message loop. | |
334 ScheduleOffscreenCallback(FlushCallbackData(callback)); | |
335 } | |
336 return PP_ERROR_WOULDBLOCK; | |
337 } | |
338 | |
339 bool DeviceContext2D::ReadImageData(PP_Resource image, | |
340 const PP_Point* top_left) { | |
341 // Get and validate the image object to paint into. | |
342 scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); | |
343 if (!image_resource) | |
344 return false; | |
345 if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL) | |
346 return false; // Must be in the right format. | |
347 | |
348 // Validate the bitmap position. | |
349 int x = top_left->x; | |
350 if (x < 0 || | |
351 static_cast<int64>(x) + static_cast<int64>(image_resource->width()) > | |
352 image_data_->width()) | |
353 return false; | |
354 int y = top_left->y; | |
355 if (y < 0 || | |
356 static_cast<int64>(y) + static_cast<int64>(image_resource->height()) > | |
357 image_data_->height()) | |
358 return false; | |
359 | |
360 ImageDataAutoMapper auto_mapper(image_resource); | |
361 if (!auto_mapper.is_valid()) | |
362 return false; | |
363 skia::PlatformCanvas* dest_canvas = image_resource->mapped_canvas(); | |
364 | |
365 SkIRect src_irect = { x, y, | |
366 x + image_resource->width(), | |
367 y + image_resource->height() }; | |
368 SkRect dest_rect = { SkIntToScalar(0), | |
369 SkIntToScalar(0), | |
370 SkIntToScalar(image_resource->width()), | |
371 SkIntToScalar(image_resource->height()) }; | |
372 | |
373 // We want to replace the contents of the bitmap rather than blend. | |
374 SkPaint paint; | |
375 paint.setXfermodeMode(SkXfermode::kSrc_Mode); | |
376 dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(), | |
377 &src_irect, dest_rect, &paint); | |
378 return true; | |
379 } | |
380 | |
381 bool DeviceContext2D::BindToInstance(PluginInstance* new_instance) { | |
382 if (bound_instance_ == new_instance) | |
383 return true; // Rebinding the same device, nothing to do. | |
384 if (bound_instance_ && new_instance) | |
385 return false; // Can't change a bound device. | |
386 | |
387 if (!new_instance) { | |
388 // When the device is detached, we'll not get any more paint callbacks so | |
389 // we need to clear the list, but we still want to issue any pending | |
390 // callbacks to the plugin. | |
391 if (!unpainted_flush_callback_.is_null()) { | |
392 ScheduleOffscreenCallback(unpainted_flush_callback_); | |
393 unpainted_flush_callback_.Clear(); | |
394 } | |
395 if (!painted_flush_callback_.is_null()) { | |
396 ScheduleOffscreenCallback(painted_flush_callback_); | |
397 painted_flush_callback_.Clear(); | |
398 } | |
399 } else if (flushed_any_data_) { | |
400 // Only schedule a paint if this backing store has had any data flushed to | |
401 // it. This is an optimization. A "normal" plugin will first allocated a | |
402 // backing store, bind it, and then execute their normal painting and | |
403 // update loop. If binding a device always invalidated, it would mean we | |
404 // would get one paint for the bind, and one for the first time the plugin | |
405 // actually painted something. By not bothering to schedule an invalidate | |
406 // when an empty device is initially bound, we can save an extra paint for | |
407 // many plugins during the critical page initialization phase. | |
408 new_instance->InvalidateRect(gfx::Rect()); | |
409 } | |
410 | |
411 bound_instance_ = new_instance; | |
412 return true; | |
413 } | |
414 | |
415 void DeviceContext2D::Paint(WebKit::WebCanvas* canvas, | |
416 const gfx::Rect& plugin_rect, | |
417 const gfx::Rect& paint_rect) { | |
418 // We're guaranteed to have a mapped canvas since we mapped it in Init(). | |
419 const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap(); | |
420 | |
421 #if defined(OS_MACOSX) | |
422 SkAutoLockPixels lock(backing_bitmap); | |
423 | |
424 scoped_cftyperef<CGDataProviderRef> data_provider( | |
425 CGDataProviderCreateWithData( | |
426 NULL, backing_bitmap.getAddr32(0, 0), | |
427 backing_bitmap.rowBytes() * backing_bitmap.height(), NULL)); | |
428 scoped_cftyperef<CGImageRef> image( | |
429 CGImageCreate( | |
430 backing_bitmap.width(), backing_bitmap.height(), | |
431 8, 32, backing_bitmap.rowBytes(), | |
432 mac_util::GetSystemColorSpace(), | |
433 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, | |
434 data_provider, NULL, false, kCGRenderingIntentDefault)); | |
435 | |
436 // Flip the transform | |
437 CGContextSaveGState(canvas); | |
438 float window_height = static_cast<float>(CGBitmapContextGetHeight(canvas)); | |
439 CGContextTranslateCTM(canvas, 0, window_height); | |
440 CGContextScaleCTM(canvas, 1.0, -1.0); | |
441 | |
442 CGRect bounds; | |
443 bounds.origin.x = plugin_rect.origin().x(); | |
444 bounds.origin.y = window_height - plugin_rect.origin().y() - | |
445 backing_bitmap.height(); | |
446 bounds.size.width = backing_bitmap.width(); | |
447 bounds.size.height = backing_bitmap.height(); | |
448 | |
449 CGContextDrawImage(canvas, bounds, image); | |
450 CGContextRestoreGState(canvas); | |
451 #else | |
452 gfx::Point origin(plugin_rect.origin().x(), plugin_rect.origin().y()); | |
453 canvas->drawBitmap(backing_bitmap, | |
454 SkIntToScalar(plugin_rect.origin().x()), | |
455 SkIntToScalar(plugin_rect.origin().y())); | |
456 #endif | |
457 } | |
458 | |
459 void DeviceContext2D::ViewInitiatedPaint() { | |
460 // Move any "unpainted" callback to the painted state. See | |
461 // |unpainted_flush_callback_| in the header for more. | |
462 if (!unpainted_flush_callback_.is_null()) { | |
463 DCHECK(painted_flush_callback_.is_null()); | |
464 std::swap(painted_flush_callback_, unpainted_flush_callback_); | |
465 } | |
466 } | |
467 | |
468 void DeviceContext2D::ViewFlushedPaint() { | |
469 // Notify any "painted" callback. See |unpainted_flush_callback_| in the | |
470 // header for more. | |
471 if (!painted_flush_callback_.is_null()) { | |
472 // We must clear this variable before issuing the callback. It will be | |
473 // common for the plugin to issue another invalidate in response to a flush | |
474 // callback, and we don't want to think that a callback is already pending. | |
475 FlushCallbackData callback; | |
476 std::swap(callback, painted_flush_callback_); | |
477 callback.Execute(PP_OK); | |
478 } | |
479 } | |
480 | |
481 void DeviceContext2D::ExecutePaintImageData(ImageData* image, | |
482 int x, int y, | |
483 const gfx::Rect& src_rect, | |
484 gfx::Rect* invalidated_rect) { | |
485 // Ensure the source image is mapped to read from it. | |
486 ImageDataAutoMapper auto_mapper(image); | |
487 if (!auto_mapper.is_valid()) | |
488 return; | |
489 | |
490 // Portion within the source image to cut out. | |
491 SkIRect src_irect = { src_rect.x(), src_rect.y(), | |
492 src_rect.right(), src_rect.bottom() }; | |
493 | |
494 // Location within the backing store to copy to. | |
495 *invalidated_rect = src_rect; | |
496 invalidated_rect->Offset(x, y); | |
497 SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()), | |
498 SkIntToScalar(invalidated_rect->y()), | |
499 SkIntToScalar(invalidated_rect->right()), | |
500 SkIntToScalar(invalidated_rect->bottom()) }; | |
501 | |
502 // We're guaranteed to have a mapped canvas since we mapped it in Init(). | |
503 skia::PlatformCanvas* backing_canvas = image_data_->mapped_canvas(); | |
504 | |
505 // We want to replace the contents of the bitmap rather than blend. | |
506 SkPaint paint; | |
507 paint.setXfermodeMode(SkXfermode::kSrc_Mode); | |
508 backing_canvas->drawBitmapRect(*image->GetMappedBitmap(), | |
509 &src_irect, dest_rect, &paint); | |
510 } | |
511 | |
512 void DeviceContext2D::ExecuteScroll(const gfx::Rect& clip, int dx, int dy, | |
513 gfx::Rect* invalidated_rect) { | |
514 gfx::ScrollCanvas(image_data_->mapped_canvas(), | |
515 clip, gfx::Point(dx, dy)); | |
516 *invalidated_rect = clip; | |
517 } | |
518 | |
519 void DeviceContext2D::ExecuteReplaceContents(ImageData* image, | |
520 gfx::Rect* invalidated_rect) { | |
521 image_data_->Swap(image); | |
522 *invalidated_rect = gfx::Rect(0, 0, | |
523 image_data_->width(), image_data_->height()); | |
524 } | |
525 | |
526 void DeviceContext2D::ScheduleOffscreenCallback( | |
527 const FlushCallbackData& callback) { | |
528 DCHECK(!HasPendingFlush()); | |
529 offscreen_flush_pending_ = true; | |
530 MessageLoop::current()->PostTask( | |
531 FROM_HERE, | |
532 NewRunnableMethod(this, | |
533 &DeviceContext2D::ExecuteOffscreenCallback, | |
534 callback)); | |
535 } | |
536 | |
537 void DeviceContext2D::ExecuteOffscreenCallback(FlushCallbackData data) { | |
538 DCHECK(offscreen_flush_pending_); | |
539 | |
540 // We must clear this flag before issuing the callback. It will be | |
541 // common for the plugin to issue another invalidate in response to a flush | |
542 // callback, and we don't want to think that a callback is already pending. | |
543 offscreen_flush_pending_ = false; | |
544 data.Execute(PP_OK); | |
545 } | |
546 | |
547 bool DeviceContext2D::HasPendingFlush() const { | |
548 return !unpainted_flush_callback_.is_null() || | |
549 !painted_flush_callback_.is_null() || | |
550 offscreen_flush_pending_; | |
551 } | |
552 | |
553 } // namespace pepper | |
OLD | NEW |