OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 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 "remoting/host/video_frame_capturer.h" | |
6 | |
7 #include <windows.h> | |
8 | |
9 #include "base/file_path.h" | |
10 #include "base/logging.h" | |
11 #include "base/memory/scoped_ptr.h" | |
12 #include "base/scoped_native_library.h" | |
13 #include "base/utf_string_conversions.h" | |
14 #include "base/win/scoped_gdi_object.h" | |
15 #include "remoting/base/capture_data.h" | |
16 #include "remoting/host/video_frame_capturer_helper.h" | |
Wez
2012/07/12 15:59:22
nit: Move below differ.h
| |
17 #include "remoting/host/desktop_win.h" | |
18 #include "remoting/host/differ.h" | |
19 #include "remoting/host/scoped_thread_desktop_win.h" | |
20 #include "remoting/proto/control.pb.h" | |
21 | |
22 namespace remoting { | |
23 | |
24 namespace { | |
25 | |
26 // Constants from dwmapi.h. | |
27 const UINT DWM_EC_DISABLECOMPOSITION = 0; | |
28 const UINT DWM_EC_ENABLECOMPOSITION = 1; | |
29 | |
30 typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); | |
31 | |
32 const char kDwmapiLibraryName[] = "dwmapi"; | |
33 | |
34 // Pixel colors used when generating cursor outlines. | |
35 const uint32 kPixelBgraBlack = 0xff000000; | |
36 const uint32 kPixelBgraWhite = 0xffffffff; | |
37 const uint32 kPixelBgraTransparent = 0x00000000; | |
38 | |
39 // VideoFrameCapturerWin captures 32bit RGB using GDI. | |
40 // | |
41 // VideoFrameCapturerWin is double-buffered as required by VideoFrameCapturer. | |
42 // See remoting/host/video_frame_capturer.h. | |
43 class VideoFrameCapturerWin : public VideoFrameCapturer { | |
44 public: | |
45 VideoFrameCapturerWin(); | |
46 virtual ~VideoFrameCapturerWin(); | |
47 | |
48 // Overridden from VideoFrameCapturer: | |
49 virtual void Start(const CursorShapeChangedCallback& callback) OVERRIDE; | |
50 virtual void Stop() OVERRIDE; | |
51 virtual void ScreenConfigurationChanged() OVERRIDE; | |
52 virtual media::VideoFrame::Format pixel_format() const OVERRIDE; | |
53 virtual void ClearInvalidRegion() OVERRIDE; | |
54 virtual void InvalidateRegion(const SkRegion& invalid_region) OVERRIDE; | |
55 virtual void InvalidateScreen(const SkISize& size) OVERRIDE; | |
56 virtual void InvalidateFullScreen() OVERRIDE; | |
57 virtual void CaptureInvalidRegion( | |
58 const CaptureCompletedCallback& callback) OVERRIDE; | |
59 virtual const SkISize& size_most_recent() const OVERRIDE; | |
60 | |
61 private: | |
62 struct VideoFrameBuffer { | |
63 VideoFrameBuffer(void* data, const SkISize& size, int bytes_per_pixel, | |
64 int bytes_per_row) | |
65 : data(data), size(size), bytes_per_pixel(bytes_per_pixel), | |
66 bytes_per_row(bytes_per_row) { | |
67 } | |
68 VideoFrameBuffer() { | |
69 data = 0; | |
70 size = SkISize::Make(0, 0); | |
71 bytes_per_pixel = 0; | |
72 bytes_per_row = 0; | |
73 } | |
74 void* data; | |
75 SkISize size; | |
76 int bytes_per_pixel; | |
77 int bytes_per_row; | |
78 }; | |
79 | |
80 // Make sure that the current buffer has the same size as the screen. | |
81 void UpdateBufferCapture(const SkISize& size); | |
82 | |
83 // Allocate memory for a buffer of a given size, freeing any memory previously | |
84 // allocated for that buffer. | |
85 void ReallocateBuffer(int buffer_index, const SkISize& size); | |
86 | |
87 void CalculateInvalidRegion(); | |
88 void CaptureRegion(const SkRegion& region, | |
89 const CaptureCompletedCallback& callback); | |
90 | |
91 void ReleaseBuffers(); | |
92 // Generates an image in the current buffer. | |
93 void CaptureImage(); | |
94 | |
95 // Expand the cursor shape to add a white outline for visibility against | |
96 // dark backgrounds. | |
97 void AddCursorOutline(int width, int height, uint32* dst); | |
98 | |
99 // Capture the current cursor shape. | |
100 void CaptureCursor(); | |
101 | |
102 // Gets the screen size. | |
103 SkISize GetScreenSize(); | |
104 | |
105 // A thread-safe list of invalid rectangles, and the size of the most | |
106 // recently captured screen. | |
107 VideoFrameCapturerHelper helper_; | |
108 | |
109 // Callback notified whenever the cursor shape is changed. | |
110 CursorShapeChangedCallback cursor_shape_changed_callback_; | |
111 | |
112 // Snapshot of the last cursor bitmap we sent to the client. This is used | |
113 // to diff against the current cursor so we only send a cursor-change | |
114 // message when the shape has changed. | |
115 scoped_array<uint8> last_cursor_; | |
116 SkISize last_cursor_size_; | |
117 | |
118 // There are two buffers for the screen images, as required by Capturer. | |
119 static const int kNumBuffers = 2; | |
120 VideoFrameBuffer buffers_[kNumBuffers]; | |
121 | |
122 ScopedThreadDesktopWin desktop_; | |
123 | |
124 // Gdi specific information about screen. | |
125 HWND desktop_window_; | |
126 HDC desktop_dc_; | |
127 HDC memory_dc_; | |
128 HBITMAP target_bitmap_[kNumBuffers]; | |
129 | |
130 // The screen size attached to the device contexts through which the screen | |
131 // is captured. | |
132 SkISize dc_size_; | |
133 | |
134 // The current buffer with valid data for reading. | |
135 int current_buffer_; | |
136 | |
137 // Format of pixels returned in buffer. | |
138 media::VideoFrame::Format pixel_format_; | |
139 | |
140 // Class to calculate the difference between two screen bitmaps. | |
141 scoped_ptr<Differ> differ_; | |
142 | |
143 base::ScopedNativeLibrary dwmapi_library_; | |
144 DwmEnableCompositionFunc composition_func_; | |
145 | |
146 DISALLOW_COPY_AND_ASSIGN(VideoFrameCapturerWin); | |
147 }; | |
148 | |
149 // 3780 pixels per meter is equivalent to 96 DPI, typical on desktop monitors. | |
150 static const int kPixelsPerMeter = 3780; | |
151 // 32 bit RGBA is 4 bytes per pixel. | |
152 static const int kBytesPerPixel = 4; | |
153 | |
154 VideoFrameCapturerWin::VideoFrameCapturerWin() | |
155 : last_cursor_size_(SkISize::Make(0, 0)), | |
156 desktop_window_(NULL), | |
157 desktop_dc_(NULL), | |
158 memory_dc_(NULL), | |
159 dc_size_(SkISize::Make(0, 0)), | |
160 current_buffer_(0), | |
161 pixel_format_(media::VideoFrame::RGB32), | |
162 composition_func_(NULL) { | |
163 memset(target_bitmap_, 0, sizeof(target_bitmap_)); | |
164 memset(buffers_, 0, sizeof(buffers_)); | |
165 ScreenConfigurationChanged(); | |
166 } | |
167 | |
168 VideoFrameCapturerWin::~VideoFrameCapturerWin() { | |
169 ReleaseBuffers(); | |
170 } | |
171 | |
172 media::VideoFrame::Format VideoFrameCapturerWin::pixel_format() const { | |
173 return pixel_format_; | |
174 } | |
175 | |
176 void VideoFrameCapturerWin::ClearInvalidRegion() { | |
177 helper_.ClearInvalidRegion(); | |
178 } | |
179 | |
180 void VideoFrameCapturerWin::InvalidateRegion(const SkRegion& invalid_region) { | |
181 helper_.InvalidateRegion(invalid_region); | |
182 } | |
183 | |
184 void VideoFrameCapturerWin::InvalidateScreen(const SkISize& size) { | |
185 helper_.InvalidateScreen(size); | |
186 } | |
187 | |
188 void VideoFrameCapturerWin::InvalidateFullScreen() { | |
189 helper_.InvalidateFullScreen(); | |
190 } | |
191 | |
192 void VideoFrameCapturerWin::CaptureInvalidRegion( | |
193 const CaptureCompletedCallback& callback) { | |
194 // Force the system to power-up display hardware, if it has been suspended. | |
195 SetThreadExecutionState(ES_DISPLAY_REQUIRED); | |
196 | |
197 // Perform the capture. | |
198 CalculateInvalidRegion(); | |
199 SkRegion invalid_region; | |
200 helper_.SwapInvalidRegion(&invalid_region); | |
201 CaptureRegion(invalid_region, callback); | |
202 | |
203 // Check for cursor shape update. | |
204 CaptureCursor(); | |
205 } | |
206 | |
207 const SkISize& VideoFrameCapturerWin::size_most_recent() const { | |
208 return helper_.size_most_recent(); | |
209 } | |
210 | |
211 void VideoFrameCapturerWin::ReleaseBuffers() { | |
212 for (int i = kNumBuffers - 1; i >= 0; i--) { | |
213 if (target_bitmap_[i]) { | |
214 DeleteObject(target_bitmap_[i]); | |
215 target_bitmap_[i] = NULL; | |
216 } | |
217 if (buffers_[i].data) { | |
218 DeleteObject(buffers_[i].data); | |
219 buffers_[i].data = NULL; | |
220 } | |
221 } | |
222 | |
223 if (desktop_dc_) { | |
224 ReleaseDC(desktop_window_, desktop_dc_); | |
225 desktop_window_ = NULL; | |
226 desktop_dc_ = NULL; | |
227 } | |
228 | |
229 if (memory_dc_) { | |
230 DeleteDC(memory_dc_); | |
231 memory_dc_ = NULL; | |
232 } | |
233 } | |
234 | |
235 void VideoFrameCapturerWin::Start( | |
236 const CursorShapeChangedCallback& callback) { | |
237 cursor_shape_changed_callback_ = callback; | |
238 | |
239 // Load dwmapi.dll dynamically since it is not available on XP. | |
240 if (!dwmapi_library_.is_valid()) { | |
241 FilePath path(base::GetNativeLibraryName(UTF8ToUTF16(kDwmapiLibraryName))); | |
242 dwmapi_library_.Reset(base::LoadNativeLibrary(path, NULL)); | |
243 } | |
244 | |
245 if (dwmapi_library_.is_valid() && composition_func_ == NULL) { | |
246 composition_func_ = static_cast<DwmEnableCompositionFunc>( | |
247 dwmapi_library_.GetFunctionPointer("DwmEnableComposition")); | |
248 } | |
249 | |
250 // Vote to disable Aero composited desktop effects while capturing. Windows | |
251 // will restore Aero automatically if the process exits. This has no effect | |
252 // under Windows 8 or higher. See crbug.com/124018. | |
253 if (composition_func_ != NULL) { | |
254 (*composition_func_)(DWM_EC_DISABLECOMPOSITION); | |
255 } | |
256 } | |
257 | |
258 void VideoFrameCapturerWin::Stop() { | |
259 // Restore Aero. | |
260 if (composition_func_ != NULL) { | |
261 (*composition_func_)(DWM_EC_ENABLECOMPOSITION); | |
262 } | |
263 } | |
264 | |
265 void VideoFrameCapturerWin::ScreenConfigurationChanged() { | |
266 // We poll for screen configuration changes, so ignore notifications. | |
267 } | |
268 | |
269 void VideoFrameCapturerWin::UpdateBufferCapture(const SkISize& size) { | |
270 // Switch to the desktop receiving user input if different from the current | |
271 // one. | |
272 scoped_ptr<DesktopWin> input_desktop = DesktopWin::GetInputDesktop(); | |
273 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { | |
274 // Release GDI resources otherwise SetThreadDesktop will fail. | |
275 if (desktop_dc_) { | |
276 ReleaseDC(desktop_window_, desktop_dc_); | |
277 desktop_window_ = NULL; | |
278 desktop_dc_ = NULL; | |
279 } | |
280 | |
281 if (memory_dc_) { | |
282 DeleteDC(memory_dc_); | |
283 memory_dc_ = NULL; | |
284 } | |
285 | |
286 ReleaseBuffers(); | |
287 | |
288 // If SetThreadDesktop() fails, the thread is still assigned a desktop. | |
289 // So we can continue capture screen bits, just from a diffented desktop. | |
290 desktop_.SetThreadDesktop(input_desktop.Pass()); | |
291 } | |
292 | |
293 // Make sure the DCs have the correct dimensions. | |
294 if (size != dc_size_) { | |
295 // TODO(simonmorris): screen dimensions changing isn't equivalent to needing | |
296 // a new DC, but it's good enough for now. | |
297 if (desktop_dc_) { | |
298 ReleaseDC(desktop_window_, desktop_dc_); | |
299 desktop_window_ = NULL; | |
300 desktop_dc_ = NULL; | |
301 } | |
302 | |
303 if (memory_dc_) { | |
304 DeleteDC(memory_dc_); | |
305 memory_dc_ = NULL; | |
306 } | |
307 } | |
308 | |
309 if (desktop_dc_ == NULL) { | |
310 DCHECK(desktop_window_ == NULL); | |
311 DCHECK(memory_dc_ == NULL); | |
312 | |
313 desktop_window_ = GetDesktopWindow(); | |
314 desktop_dc_ = GetDC(desktop_window_); | |
315 memory_dc_ = CreateCompatibleDC(desktop_dc_); | |
316 dc_size_ = size; | |
317 } | |
318 | |
319 // Make sure the current bitmap has the correct dimensions. | |
320 if (buffers_[current_buffer_].data == NULL || | |
321 size != buffers_[current_buffer_].size) { | |
322 ReallocateBuffer(current_buffer_, size); | |
323 InvalidateFullScreen(); | |
324 } | |
325 } | |
326 | |
327 void VideoFrameCapturerWin::ReallocateBuffer(int buffer_index, | |
328 const SkISize& size) { | |
329 // Delete any previously constructed bitmap. | |
330 if (target_bitmap_[buffer_index]) { | |
331 DeleteObject(target_bitmap_[buffer_index]); | |
332 target_bitmap_[buffer_index] = NULL; | |
333 } | |
334 if (buffers_[buffer_index].data) { | |
335 DeleteObject(buffers_[buffer_index].data); | |
336 buffers_[buffer_index].data = NULL; | |
337 } | |
338 | |
339 // Create a bitmap to keep the desktop image. | |
340 int rounded_width = (size.width() + 3) & (~3); | |
341 | |
342 // Dimensions of screen. | |
343 pixel_format_ = media::VideoFrame::RGB32; | |
344 int bytes_per_row = rounded_width * kBytesPerPixel; | |
345 | |
346 // Create a device independent bitmap (DIB) that is the same size. | |
347 BITMAPINFO bmi; | |
348 memset(&bmi, 0, sizeof(bmi)); | |
349 bmi.bmiHeader.biHeight = -size.height(); | |
350 bmi.bmiHeader.biWidth = rounded_width; | |
351 bmi.bmiHeader.biPlanes = 1; | |
352 bmi.bmiHeader.biBitCount = kBytesPerPixel * 8; | |
353 bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); | |
354 bmi.bmiHeader.biSizeImage = bytes_per_row * size.height(); | |
355 bmi.bmiHeader.biXPelsPerMeter = kPixelsPerMeter; | |
356 bmi.bmiHeader.biYPelsPerMeter = kPixelsPerMeter; | |
357 | |
358 // Create memory for the buffers. | |
359 target_bitmap_[buffer_index] = | |
360 CreateDIBSection(desktop_dc_, &bmi, DIB_RGB_COLORS, | |
361 static_cast<void**>(&buffers_[buffer_index].data), | |
362 NULL, 0); | |
363 buffers_[buffer_index].size = SkISize::Make(bmi.bmiHeader.biWidth, | |
364 std::abs(bmi.bmiHeader.biHeight)); | |
365 buffers_[buffer_index].bytes_per_pixel = bmi.bmiHeader.biBitCount / 8; | |
366 buffers_[buffer_index].bytes_per_row = | |
367 bmi.bmiHeader.biSizeImage / std::abs(bmi.bmiHeader.biHeight); | |
368 } | |
369 | |
370 void VideoFrameCapturerWin::CalculateInvalidRegion() { | |
371 CaptureImage(); | |
372 | |
373 const VideoFrameBuffer& current = buffers_[current_buffer_]; | |
374 | |
375 // Find the previous and current screens. | |
376 int prev_buffer_id = current_buffer_ - 1; | |
377 if (prev_buffer_id < 0) { | |
378 prev_buffer_id = kNumBuffers - 1; | |
379 } | |
380 const VideoFrameBuffer& prev = buffers_[prev_buffer_id]; | |
381 | |
382 // Maybe the previous and current screens can't be differenced. | |
383 if ((current.size != prev.size) || | |
384 (current.bytes_per_pixel != prev.bytes_per_pixel) || | |
385 (current.bytes_per_row != prev.bytes_per_row)) { | |
386 InvalidateScreen(current.size); | |
387 return; | |
388 } | |
389 | |
390 // Make sure the differencer is set up correctly for these previous and | |
391 // current screens. | |
392 if (!differ_.get() || | |
393 (differ_->width() != current.size.width()) || | |
394 (differ_->height() != current.size.height()) || | |
395 (differ_->bytes_per_pixel() != current.bytes_per_pixel) || | |
396 (differ_->bytes_per_row() != current.bytes_per_row)) { | |
397 differ_.reset(new Differ(current.size.width(), current.size.height(), | |
398 current.bytes_per_pixel, current.bytes_per_row)); | |
399 } | |
400 | |
401 SkRegion region; | |
402 differ_->CalcDirtyRegion(prev.data, current.data, ®ion); | |
403 | |
404 InvalidateRegion(region); | |
405 } | |
406 | |
407 void VideoFrameCapturerWin::CaptureRegion( | |
408 const SkRegion& region, | |
409 const CaptureCompletedCallback& callback) { | |
410 const VideoFrameBuffer& buffer = buffers_[current_buffer_]; | |
411 current_buffer_ = (current_buffer_ + 1) % kNumBuffers; | |
412 | |
413 DataPlanes planes; | |
414 planes.data[0] = static_cast<uint8*>(buffer.data); | |
415 planes.strides[0] = buffer.bytes_per_row; | |
416 | |
417 scoped_refptr<CaptureData> data(new CaptureData(planes, | |
418 buffer.size, | |
419 pixel_format_)); | |
420 data->mutable_dirty_region() = region; | |
421 | |
422 helper_.set_size_most_recent(data->size()); | |
423 | |
424 callback.Run(data); | |
425 } | |
426 | |
427 void VideoFrameCapturerWin::CaptureImage() { | |
428 // Make sure the structures we use to capture the image have the correct size. | |
429 UpdateBufferCapture(GetScreenSize()); | |
430 | |
431 // Select the target bitmap into the memory dc. | |
432 SelectObject(memory_dc_, target_bitmap_[current_buffer_]); | |
433 | |
434 // And then copy the rect from desktop to memory. | |
435 BitBlt(memory_dc_, 0, 0, buffers_[current_buffer_].size.width(), | |
436 buffers_[current_buffer_].size.height(), desktop_dc_, 0, 0, | |
437 SRCCOPY | CAPTUREBLT); | |
438 } | |
439 | |
440 void VideoFrameCapturerWin::AddCursorOutline(int width, | |
441 int height, | |
442 uint32* dst) { | |
443 for (int y = 0; y < height; y++) { | |
444 for (int x = 0; x < width; x++) { | |
445 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the | |
446 // neighbor pixels to see if this should be changed to an outline pixel. | |
447 if (*dst == kPixelBgraTransparent) { | |
448 // Change to white pixel if any neighbors (top, bottom, left, right) | |
449 // are black. | |
450 if ((y > 0 && dst[-width] == kPixelBgraBlack) || | |
451 (y < height - 1 && dst[width] == kPixelBgraBlack) || | |
452 (x > 0 && dst[-1] == kPixelBgraBlack) || | |
453 (x < width - 1 && dst[1] == kPixelBgraBlack)) { | |
454 *dst = kPixelBgraWhite; | |
455 } | |
456 } | |
457 dst++; | |
458 } | |
459 } | |
460 } | |
461 | |
462 void VideoFrameCapturerWin::CaptureCursor() { | |
463 CURSORINFO cursor_info; | |
464 cursor_info.cbSize = sizeof(CURSORINFO); | |
465 if (!GetCursorInfo(&cursor_info)) { | |
466 VLOG(3) << "Unable to get cursor info. Error = " << GetLastError(); | |
467 return; | |
468 } | |
469 | |
470 // Note that this does not need to be freed. | |
471 HCURSOR hcursor = cursor_info.hCursor; | |
472 ICONINFO iinfo; | |
473 if (!GetIconInfo(hcursor, &iinfo)) { | |
474 VLOG(3) << "Unable to get cursor icon info. Error = " << GetLastError(); | |
475 return; | |
476 } | |
477 int hotspot_x = iinfo.xHotspot; | |
478 int hotspot_y = iinfo.yHotspot; | |
479 | |
480 // Get the cursor bitmap. | |
481 base::win::ScopedBitmap hbitmap; | |
482 BITMAP bitmap; | |
483 bool color_bitmap; | |
484 if (iinfo.hbmColor) { | |
485 // Color cursor bitmap. | |
486 color_bitmap = true; | |
487 hbitmap.Set((HBITMAP)CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0, | |
488 LR_CREATEDIBSECTION)); | |
489 if (!hbitmap.Get()) { | |
490 VLOG(3) << "Unable to copy color cursor image. Error = " | |
491 << GetLastError(); | |
492 return; | |
493 } | |
494 | |
495 // Free the color and mask bitmaps since we only need our copy. | |
496 DeleteObject(iinfo.hbmColor); | |
497 DeleteObject(iinfo.hbmMask); | |
498 } else { | |
499 // Black and white (xor) cursor. | |
500 color_bitmap = false; | |
501 hbitmap.Set(iinfo.hbmMask); | |
502 } | |
503 | |
504 if (!GetObject(hbitmap.Get(), sizeof(BITMAP), &bitmap)) { | |
505 VLOG(3) << "Unable to get cursor bitmap. Error = " << GetLastError(); | |
506 return; | |
507 } | |
508 | |
509 int width = bitmap.bmWidth; | |
510 int height = bitmap.bmHeight; | |
511 // For non-color cursors, the mask contains both an AND and an XOR mask and | |
512 // the height includes both. Thus, the width is correct, but we need to | |
513 // divide by 2 to get the correct mask height. | |
514 if (!color_bitmap) { | |
515 height /= 2; | |
516 } | |
517 int data_size = height * width * kBytesPerPixel; | |
518 | |
519 scoped_ptr<protocol::CursorShapeInfo> cursor_proto( | |
520 new protocol::CursorShapeInfo()); | |
521 cursor_proto->mutable_data()->resize(data_size); | |
522 uint8* cursor_dst_data = const_cast<uint8*>(reinterpret_cast<const uint8*>( | |
523 cursor_proto->mutable_data()->data())); | |
524 | |
525 // Copy/convert cursor bitmap into format needed by chromotocol. | |
526 int row_bytes = bitmap.bmWidthBytes; | |
527 if (color_bitmap) { | |
528 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) { | |
529 VLOG(3) << "Unsupported color cursor format. Error = " << GetLastError(); | |
530 return; | |
531 } | |
532 | |
533 // Cursor bitmap is stored upside-down on Windows. Flip the rows and store | |
534 // it in the proto. | |
535 uint8* cursor_src_data = reinterpret_cast<uint8*>(bitmap.bmBits); | |
536 uint8* src = cursor_src_data + ((height - 1) * row_bytes); | |
537 uint8* dst = cursor_dst_data; | |
538 for (int row = 0; row < height; row++) { | |
539 memcpy(dst, src, row_bytes); | |
540 dst += width * kBytesPerPixel; | |
541 src -= row_bytes; | |
542 } | |
543 } else { | |
544 if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) { | |
545 VLOG(3) << "Unsupported cursor mask format. Error = " << GetLastError(); | |
546 return; | |
547 } | |
548 | |
549 // x2 because there are 2 masks in the bitmap: AND and XOR. | |
550 int mask_bytes = height * row_bytes * 2; | |
551 scoped_array<uint8> mask(new uint8[mask_bytes]); | |
552 if (!GetBitmapBits(hbitmap.Get(), mask_bytes, mask.get())) { | |
553 VLOG(3) << "Unable to get cursor mask bits. Error = " << GetLastError(); | |
554 return; | |
555 } | |
556 uint8* and_mask = mask.get(); | |
557 uint8* xor_mask = mask.get() + height * row_bytes; | |
558 uint8* dst = cursor_dst_data; | |
559 bool add_outline = false; | |
560 for (int y = 0; y < height; y++) { | |
561 for (int x = 0; x < width; x++) { | |
562 int byte = y * row_bytes + x / 8; | |
563 int bit = 7 - x % 8; | |
564 int and = and_mask[byte] & (1 << bit); | |
565 int xor = xor_mask[byte] & (1 << bit); | |
566 | |
567 // The two cursor masks combine as follows: | |
568 // AND XOR Windows Result Our result RGB Alpha | |
569 // 0 0 Black Black 00 ff | |
570 // 0 1 White White ff ff | |
571 // 1 0 Screen Transparent 00 00 | |
572 // 1 1 Reverse-screen Black 00 ff | |
573 // Since we don't support XOR cursors, we replace the "Reverse Screen" | |
574 // with black. In this case, we also add an outline around the cursor | |
575 // so that it is visible against a dark background. | |
576 int rgb = (!and && xor) ? 0xff : 0x00; | |
577 int alpha = (and && !xor) ? 0x00 : 0xff; | |
578 *dst++ = rgb; | |
579 *dst++ = rgb; | |
580 *dst++ = rgb; | |
581 *dst++ = alpha; | |
582 if (and && xor) { | |
583 add_outline = true; | |
584 } | |
585 } | |
586 } | |
587 if (add_outline) { | |
588 AddCursorOutline(width, height, | |
589 reinterpret_cast<uint32*>(cursor_dst_data)); | |
590 } | |
591 } | |
592 | |
593 // Compare the current cursor with the last one we sent to the client. If | |
594 // they're the same, then don't bother sending the cursor again. | |
595 if (last_cursor_size_.equals(width, height) && | |
596 memcmp(last_cursor_.get(), cursor_dst_data, data_size) == 0) { | |
597 return; | |
598 } | |
599 | |
600 VLOG(3) << "Sending updated cursor: " << width << "x" << height; | |
601 | |
602 cursor_proto->set_width(width); | |
603 cursor_proto->set_height(height); | |
604 cursor_proto->set_hotspot_x(hotspot_x); | |
605 cursor_proto->set_hotspot_y(hotspot_y); | |
606 | |
607 // Record the last cursor image that we sent to the client. | |
608 last_cursor_.reset(new uint8[data_size]); | |
609 memcpy(last_cursor_.get(), cursor_dst_data, data_size); | |
610 last_cursor_size_ = SkISize::Make(width, height); | |
611 | |
612 cursor_shape_changed_callback_.Run(cursor_proto.Pass()); | |
613 } | |
614 | |
615 SkISize VideoFrameCapturerWin::GetScreenSize() { | |
616 return SkISize::Make(GetSystemMetrics(SM_CXSCREEN), | |
617 GetSystemMetrics(SM_CYSCREEN)); | |
618 } | |
619 | |
620 } // namespace | |
621 | |
622 // static | |
623 VideoFrameCapturer* VideoFrameCapturer::Create() { | |
624 return new VideoFrameCapturerWin(); | |
625 } | |
626 | |
627 } // namespace remoting | |
OLD | NEW |