// Copyright 2014 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 "content/browser/devtools/protocol/color_picker.h" #include "base/bind.h" #include "build/build_config.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/renderer_host/render_widget_host_view_base.h" #include "content/common/cursors/webcursor.h" #include "third_party/WebKit/public/platform/WebCursorInfo.h" #include "third_party/WebKit/public/platform/WebScreenInfo.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPath.h" #include "ui/gfx/geometry/size_conversions.h" namespace content { namespace devtools { namespace page { ColorPicker::ColorPicker(ColorPickedCallback callback) : callback_(callback), enabled_(false), last_cursor_x_(-1), last_cursor_y_(-1), host_(nullptr), weak_factory_(this) { mouse_event_callback_ = base::Bind( &ColorPicker::HandleMouseEvent, base::Unretained(this)); } ColorPicker::~ColorPicker() { } void ColorPicker::SetRenderWidgetHost(RenderWidgetHostImpl* host) { if (host_ == host) return; if (enabled_ && host_) host_->RemoveMouseEventCallback(mouse_event_callback_); ResetFrame(); host_ = host; if (enabled_ && host) host->AddMouseEventCallback(mouse_event_callback_); } void ColorPicker::SetEnabled(bool enabled) { if (enabled_ == enabled) return; enabled_ = enabled; if (!host_) return; if (enabled) { host_->AddMouseEventCallback(mouse_event_callback_); UpdateFrame(); } else { host_->RemoveMouseEventCallback(mouse_event_callback_); ResetFrame(); WebCursor pointer_cursor; WebCursor::CursorInfo cursor_info; cursor_info.type = blink::WebCursorInfo::TypePointer; pointer_cursor.InitFromCursorInfo(cursor_info); host_->SetCursor(pointer_cursor); } } void ColorPicker::OnSwapCompositorFrame() { if (enabled_) UpdateFrame(); } void ColorPicker::UpdateFrame() { if (!host_) return; RenderWidgetHostViewBase* view = static_cast(host_->GetView()); if (!view) return; gfx::Size size = view->GetViewBounds().size(); view->CopyFromCompositingSurface( gfx::Rect(size), size, base::Bind(&ColorPicker::FrameUpdated, weak_factory_.GetWeakPtr()), kN32_SkColorType); } void ColorPicker::ResetFrame() { frame_.reset(); last_cursor_x_ = -1; last_cursor_y_ = -1; } void ColorPicker::FrameUpdated(const SkBitmap& bitmap, ReadbackResponse response) { if (!enabled_) return; if (response == READBACK_SUCCESS) { frame_ = bitmap; UpdateCursor(); } } bool ColorPicker::HandleMouseEvent(const blink::WebMouseEvent& event) { last_cursor_x_ = event.x; last_cursor_y_ = event.y; if (frame_.drawsNothing()) return true; if (event.button == blink::WebMouseEvent::ButtonLeft && event.type == blink::WebInputEvent::MouseDown) { if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) { return true; } SkAutoLockPixels lock_image(frame_); SkColor sk_color = frame_.getColor(last_cursor_x_, last_cursor_y_); callback_.Run(SkColorGetR(sk_color), SkColorGetG(sk_color), SkColorGetB(sk_color), SkColorGetA(sk_color)); } UpdateCursor(); return true; } void ColorPicker::UpdateCursor() { if (!host_ || frame_.drawsNothing()) return; if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) { return; } RenderWidgetHostViewBase* view = static_cast( host_->GetView()); if (!view) return; // Due to platform limitations, we are using two different cursors // depending on the platform. Mac and Win have large cursors with two circles // for original spot and its magnified projection; Linux gets smaller (64 px) // magnified projection only with centered hotspot. // Mac Retina requires cursor to be > 120px in order to render smoothly. #if defined(OS_LINUX) const float kCursorSize = 63; const float kDiameter = 63; const float kHotspotOffset = 32; const float kHotspotRadius = 0; const float kPixelSize = 9; #else const float kCursorSize = 150; const float kDiameter = 110; const float kHotspotOffset = 25; const float kHotspotRadius = 5; const float kPixelSize = 10; #endif blink::WebScreenInfo screen_info; view->GetScreenInfo(&screen_info); double device_scale_factor = screen_info.deviceScaleFactor; SkBitmap result; result.allocN32Pixels(kCursorSize * device_scale_factor, kCursorSize * device_scale_factor); result.eraseARGB(0, 0, 0, 0); SkCanvas canvas(result); canvas.scale(device_scale_factor, device_scale_factor); canvas.translate(0.5f, 0.5f); SkPaint paint; // Paint original spot with cross. if (kHotspotRadius) { paint.setStrokeWidth(1); paint.setAntiAlias(false); paint.setColor(SK_ColorDKGRAY); paint.setStyle(SkPaint::kStroke_Style); canvas.drawLine(kHotspotOffset, kHotspotOffset - 2 * kHotspotRadius, kHotspotOffset, kHotspotOffset - kHotspotRadius, paint); canvas.drawLine(kHotspotOffset, kHotspotOffset + kHotspotRadius, kHotspotOffset, kHotspotOffset + 2 * kHotspotRadius, paint); canvas.drawLine(kHotspotOffset - 2 * kHotspotRadius, kHotspotOffset, kHotspotOffset - kHotspotRadius, kHotspotOffset, paint); canvas.drawLine(kHotspotOffset + kHotspotRadius, kHotspotOffset, kHotspotOffset + 2 * kHotspotRadius, kHotspotOffset, paint); paint.setStrokeWidth(2); paint.setAntiAlias(true); canvas.drawCircle(kHotspotOffset, kHotspotOffset, kHotspotRadius, paint); } // Clip circle for magnified projection. float padding = (kCursorSize - kDiameter) / 2; SkPath clip_path; clip_path.addOval(SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter)); clip_path.close(); canvas.clipPath(clip_path, SkRegion::kIntersect_Op, true); // Project pixels. int pixel_count = kDiameter / kPixelSize; SkRect src_rect = SkRect::MakeXYWH(last_cursor_x_ - pixel_count / 2, last_cursor_y_ - pixel_count / 2, pixel_count, pixel_count); SkRect dst_rect = SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter); canvas.drawBitmapRect(frame_, src_rect, dst_rect, NULL); // Paint grid. paint.setStrokeWidth(1); paint.setAntiAlias(false); paint.setColor(SK_ColorGRAY); for (int i = 0; i < pixel_count; ++i) { canvas.drawLine(padding + i * kPixelSize, padding, padding + i * kPixelSize, kCursorSize - padding, paint); canvas.drawLine(padding, padding + i * kPixelSize, kCursorSize - padding, padding + i * kPixelSize, paint); } // Paint central pixel in red. SkRect pixel = SkRect::MakeXYWH((kCursorSize - kPixelSize) / 2, (kCursorSize - kPixelSize) / 2, kPixelSize, kPixelSize); paint.setColor(SK_ColorRED); paint.setStyle(SkPaint::kStroke_Style); canvas.drawRect(pixel, paint); // Paint outline. paint.setStrokeWidth(2); paint.setColor(SK_ColorDKGRAY); paint.setAntiAlias(true); canvas.drawCircle(kCursorSize / 2, kCursorSize / 2, kDiameter / 2, paint); WebCursor cursor; WebCursor::CursorInfo cursor_info; cursor_info.type = blink::WebCursorInfo::TypeCustom; cursor_info.image_scale_factor = device_scale_factor; cursor_info.custom_image = result; cursor_info.hotspot = gfx::Point(kHotspotOffset * device_scale_factor, kHotspotOffset * device_scale_factor); #if defined(OS_WIN) cursor_info.external_handle = 0; #endif cursor.InitFromCursorInfo(cursor_info); DCHECK(host_); host_->SetCursor(cursor); } } // namespace page } // namespace devtools } // namespace content