Allow the screen capturer to capture oversized cursors and cursors without alpha channel (Windows).
Changes in this CL: - CaptureCursor() scans the cursor to verify that it has alpha channel. - The AND mask of the cursor is used to reconstruct transparency if the cursor does not have alpha channel. - CaptureCursor() always outlines the cursor when a "screen reverse" pixel detected. Previously it was only done for black and while cursors. Added desktop_capture_unittest.MouseCursorShapeTest to test the cursor conversion code. BUG=chromium:223147 R=sergeyu@chromium.org Review URL: https://webrtc-codereview.appspot.com/1627004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4210 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
5e03f8ab67
commit
4af0878e57
@ -55,8 +55,11 @@
|
|||||||
"shared_desktop_frame.h",
|
"shared_desktop_frame.h",
|
||||||
"shared_memory.cc",
|
"shared_memory.cc",
|
||||||
"shared_memory.h",
|
"shared_memory.h",
|
||||||
|
"win/cursor.cc",
|
||||||
|
"win/cursor.h",
|
||||||
"win/desktop.cc",
|
"win/desktop.cc",
|
||||||
"win/desktop.h",
|
"win/desktop.h",
|
||||||
|
"win/scoped_gdi_object.h",
|
||||||
"win/scoped_thread_desktop.cc",
|
"win/scoped_thread_desktop.cc",
|
||||||
"win/scoped_thread_desktop.h",
|
"win/scoped_thread_desktop.h",
|
||||||
"window_capturer.h",
|
"window_capturer.h",
|
||||||
@ -143,6 +146,9 @@
|
|||||||
"screen_capturer_mock_objects.h",
|
"screen_capturer_mock_objects.h",
|
||||||
"screen_capturer_unittest.cc",
|
"screen_capturer_unittest.cc",
|
||||||
"window_capturer_unittest.cc",
|
"window_capturer_unittest.cc",
|
||||||
|
"win/cursor_unittest.cc",
|
||||||
|
"win/cursor_unittest_resources.h",
|
||||||
|
"win/cursor_unittest_resources.rc",
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
# Run screen/window capturer tests only on platforms where they are
|
# Run screen/window capturer tests only on platforms where they are
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h"
|
#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h"
|
||||||
#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
|
#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
|
||||||
#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
|
#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/win/cursor.h"
|
||||||
#include "webrtc/modules/desktop_capture/win/desktop.h"
|
#include "webrtc/modules/desktop_capture/win/desktop.h"
|
||||||
#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
|
#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
|
||||||
#include "webrtc/system_wrappers/interface/logging.h"
|
#include "webrtc/system_wrappers/interface/logging.h"
|
||||||
@ -37,15 +38,6 @@ typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
|
|||||||
|
|
||||||
const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
|
const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
|
||||||
|
|
||||||
// Pixel colors used when generating cursor outlines.
|
|
||||||
const uint32_t kPixelBgraBlack = 0xff000000;
|
|
||||||
const uint32_t kPixelBgraWhite = 0xffffffff;
|
|
||||||
const uint32_t kPixelBgraTransparent = 0x00000000;
|
|
||||||
|
|
||||||
uint8_t AlphaMul(uint8_t v, uint8_t alpha) {
|
|
||||||
return (static_cast<uint16_t>(v) * alpha) >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScreenCapturerWin captures 32bit RGB using GDI.
|
// ScreenCapturerWin captures 32bit RGB using GDI.
|
||||||
//
|
//
|
||||||
// ScreenCapturerWin is double-buffered as required by ScreenCapturer.
|
// ScreenCapturerWin is double-buffered as required by ScreenCapturer.
|
||||||
@ -67,10 +59,6 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
// Captures the current screen contents into the current buffer.
|
// Captures the current screen contents into the current buffer.
|
||||||
void CaptureImage();
|
void CaptureImage();
|
||||||
|
|
||||||
// Expand the cursor shape to add a white outline for visibility against
|
|
||||||
// dark backgrounds.
|
|
||||||
void AddCursorOutline(int width, int height, uint32_t* dst);
|
|
||||||
|
|
||||||
// Capture the current cursor shape.
|
// Capture the current cursor shape.
|
||||||
void CaptureCursor();
|
void CaptureCursor();
|
||||||
|
|
||||||
@ -330,178 +318,19 @@ void ScreenCapturerWin::CaptureImage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenCapturerWin::AddCursorOutline(int width,
|
|
||||||
int height,
|
|
||||||
uint32_t* dst) {
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
// If this is a transparent pixel (bgr == 0 and alpha = 0), check the
|
|
||||||
// neighbor pixels to see if this should be changed to an outline pixel.
|
|
||||||
if (*dst == kPixelBgraTransparent) {
|
|
||||||
// Change to white pixel if any neighbors (top, bottom, left, right)
|
|
||||||
// are black.
|
|
||||||
if ((y > 0 && dst[-width] == kPixelBgraBlack) ||
|
|
||||||
(y < height - 1 && dst[width] == kPixelBgraBlack) ||
|
|
||||||
(x > 0 && dst[-1] == kPixelBgraBlack) ||
|
|
||||||
(x < width - 1 && dst[1] == kPixelBgraBlack)) {
|
|
||||||
*dst = kPixelBgraWhite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dst++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScreenCapturerWin::CaptureCursor() {
|
void ScreenCapturerWin::CaptureCursor() {
|
||||||
CURSORINFO cursor_info;
|
CURSORINFO cursor_info;
|
||||||
cursor_info.cbSize = sizeof(CURSORINFO);
|
cursor_info.cbSize = sizeof(CURSORINFO);
|
||||||
if (!GetCursorInfo(&cursor_info)) {
|
if (!GetCursorInfo(&cursor_info)) {
|
||||||
LOG_F(LS_INFO) << "Unable to get cursor info. Error = " << GetLastError();
|
LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this does not need to be freed.
|
// Note that |cursor_info.hCursor| does not need to be freed.
|
||||||
HCURSOR hcursor = cursor_info.hCursor;
|
scoped_ptr<MouseCursorShape> cursor(
|
||||||
ICONINFO iinfo;
|
CreateMouseCursorShapeFromCursor(desktop_dc_, cursor_info.hCursor));
|
||||||
if (!GetIconInfo(hcursor, &iinfo)) {
|
if (!cursor.get())
|
||||||
LOG_F(LS_INFO) << "Unable to get cursor icon info. Error = "
|
|
||||||
<< GetLastError();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
int hotspot_x = iinfo.xHotspot;
|
|
||||||
int hotspot_y = iinfo.yHotspot;
|
|
||||||
|
|
||||||
// Get the cursor bitmap.
|
|
||||||
HBITMAP hbitmap;
|
|
||||||
BITMAP bitmap;
|
|
||||||
bool color_bitmap;
|
|
||||||
if (iinfo.hbmColor) {
|
|
||||||
// Color cursor bitmap.
|
|
||||||
color_bitmap = true;
|
|
||||||
hbitmap = reinterpret_cast<HBITMAP>(
|
|
||||||
CopyImage(iinfo.hbmColor, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION));
|
|
||||||
if (!hbitmap) {
|
|
||||||
LOG_F(LS_INFO) << "Unable to copy color cursor image. Error = "
|
|
||||||
<< GetLastError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free the color and mask bitmaps since we only need our copy.
|
|
||||||
DeleteObject(iinfo.hbmColor);
|
|
||||||
DeleteObject(iinfo.hbmMask);
|
|
||||||
} else {
|
|
||||||
// Black and white (xor) cursor.
|
|
||||||
color_bitmap = false;
|
|
||||||
hbitmap = iinfo.hbmMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!GetObject(hbitmap, sizeof(BITMAP), &bitmap)) {
|
|
||||||
LOG_F(LS_INFO) << "Unable to get cursor bitmap. Error = " << GetLastError();
|
|
||||||
DeleteObject(hbitmap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = bitmap.bmWidth;
|
|
||||||
int height = bitmap.bmHeight;
|
|
||||||
// For non-color cursors, the mask contains both an AND and an XOR mask and
|
|
||||||
// the height includes both. Thus, the width is correct, but we need to
|
|
||||||
// divide by 2 to get the correct mask height.
|
|
||||||
if (!color_bitmap) {
|
|
||||||
height /= 2;
|
|
||||||
}
|
|
||||||
int data_size = height * width * DesktopFrame::kBytesPerPixel;
|
|
||||||
|
|
||||||
scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape());
|
|
||||||
cursor->data.resize(data_size);
|
|
||||||
uint8_t* cursor_dst_data =
|
|
||||||
reinterpret_cast<uint8_t*>(&*(cursor->data.begin()));
|
|
||||||
|
|
||||||
// Copy/convert cursor bitmap into format needed by chromotocol.
|
|
||||||
int row_bytes = bitmap.bmWidthBytes;
|
|
||||||
if (color_bitmap) {
|
|
||||||
if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 32) {
|
|
||||||
LOG_F(LS_INFO) << "Unsupported color cursor format. Error = "
|
|
||||||
<< GetLastError();
|
|
||||||
DeleteObject(hbitmap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy across colour cursor imagery.
|
|
||||||
// MouseCursorShape stores imagery top-down, and premultiplied
|
|
||||||
// by the alpha channel, whereas windows stores them bottom-up
|
|
||||||
// and not premultiplied.
|
|
||||||
uint8_t* cursor_src_data = reinterpret_cast<uint8_t*>(bitmap.bmBits);
|
|
||||||
uint8_t* src = cursor_src_data + ((height - 1) * row_bytes);
|
|
||||||
uint8_t* dst = cursor_dst_data;
|
|
||||||
for (int row = 0; row < height; ++row) {
|
|
||||||
for (int column = 0; column < width; ++column) {
|
|
||||||
dst[0] = AlphaMul(src[0], src[3]);
|
|
||||||
dst[1] = AlphaMul(src[1], src[3]);
|
|
||||||
dst[2] = AlphaMul(src[2], src[3]);
|
|
||||||
dst[3] = src[3];
|
|
||||||
dst += DesktopFrame::kBytesPerPixel;
|
|
||||||
src += DesktopFrame::kBytesPerPixel;
|
|
||||||
}
|
|
||||||
src -= row_bytes + (width * DesktopFrame::kBytesPerPixel);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (bitmap.bmPlanes != 1 || bitmap.bmBitsPixel != 1) {
|
|
||||||
LOG(LS_VERBOSE) << "Unsupported cursor mask format. Error = "
|
|
||||||
<< GetLastError();
|
|
||||||
DeleteObject(hbitmap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// x2 because there are 2 masks in the bitmap: AND and XOR.
|
|
||||||
int mask_bytes = height * row_bytes * 2;
|
|
||||||
scoped_array<uint8_t> mask(new uint8_t[mask_bytes]);
|
|
||||||
if (!GetBitmapBits(hbitmap, mask_bytes, mask.get())) {
|
|
||||||
LOG(LS_VERBOSE) << "Unable to get cursor mask bits. Error = "
|
|
||||||
<< GetLastError();
|
|
||||||
DeleteObject(hbitmap);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t* and_mask = mask.get();
|
|
||||||
uint8_t* xor_mask = mask.get() + height * row_bytes;
|
|
||||||
uint8_t* dst = cursor_dst_data;
|
|
||||||
bool add_outline = false;
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int byte = y * row_bytes + x / 8;
|
|
||||||
int bit = 7 - x % 8;
|
|
||||||
int and_bit = and_mask[byte] & (1 << bit);
|
|
||||||
int xor_bit = xor_mask[byte] & (1 << bit);
|
|
||||||
|
|
||||||
// The two cursor masks combine as follows:
|
|
||||||
// AND XOR Windows Result Our result RGB Alpha
|
|
||||||
// 0 0 Black Black 00 ff
|
|
||||||
// 0 1 White White ff ff
|
|
||||||
// 1 0 Screen Transparent 00 00
|
|
||||||
// 1 1 Reverse-screen Black 00 ff
|
|
||||||
// Since we don't support XOR cursors, we replace the "Reverse Screen"
|
|
||||||
// with black. In this case, we also add an outline around the cursor
|
|
||||||
// so that it is visible against a dark background.
|
|
||||||
int rgb = (!and_bit && xor_bit) ? 0xff : 0x00;
|
|
||||||
int alpha = (and_bit && !xor_bit) ? 0x00 : 0xff;
|
|
||||||
*dst++ = rgb;
|
|
||||||
*dst++ = rgb;
|
|
||||||
*dst++ = rgb;
|
|
||||||
*dst++ = alpha;
|
|
||||||
if (and_bit && xor_bit) {
|
|
||||||
add_outline = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (add_outline) {
|
|
||||||
AddCursorOutline(width, height,
|
|
||||||
reinterpret_cast<uint32_t*>(cursor_dst_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteObject(hbitmap);
|
|
||||||
|
|
||||||
cursor->size.set(width, height);
|
|
||||||
cursor->hotspot.set(hotspot_x, hotspot_y);
|
|
||||||
|
|
||||||
// Compare the current cursor with the last one we sent to the client. If
|
// Compare the current cursor with the last one we sent to the client. If
|
||||||
// they're the same, then don't bother sending the cursor again.
|
// they're the same, then don't bother sending the cursor again.
|
||||||
@ -511,7 +340,8 @@ void ScreenCapturerWin::CaptureCursor() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(LS_VERBOSE) << "Sending updated cursor: " << width << "x" << height;
|
LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x"
|
||||||
|
<< cursor->size.height();
|
||||||
|
|
||||||
// Record the last cursor image that we sent to the client.
|
// Record the last cursor image that we sent to the client.
|
||||||
last_cursor_ = *cursor;
|
last_cursor_ = *cursor;
|
||||||
|
255
webrtc/modules/desktop_capture/win/cursor.cc
Normal file
255
webrtc/modules/desktop_capture/win/cursor.cc
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "webrtc/modules/desktop_capture/win/cursor.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/desktop_frame.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
|
||||||
|
#include "webrtc/system_wrappers/interface/compile_assert.h"
|
||||||
|
#include "webrtc/system_wrappers/interface/logging.h"
|
||||||
|
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||||
|
#include "webrtc/typedefs.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
|
||||||
|
|
||||||
|
#define RGBA(r, g, b, a) \
|
||||||
|
((((a) << 24) & 0xff000000) | \
|
||||||
|
(((b) << 16) & 0xff0000) | \
|
||||||
|
(((g) << 8) & 0xff00) | \
|
||||||
|
((r) & 0xff))
|
||||||
|
|
||||||
|
#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
|
||||||
|
|
||||||
|
#define RGBA(r, g, b, a) \
|
||||||
|
((((r) << 24) & 0xff000000) | \
|
||||||
|
(((g) << 16) & 0xff0000) | \
|
||||||
|
(((b) << 8) & 0xff00) | \
|
||||||
|
((a) & 0xff))
|
||||||
|
|
||||||
|
#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
|
||||||
|
|
||||||
|
const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
|
||||||
|
|
||||||
|
// Pixel colors used when generating cursor outlines.
|
||||||
|
const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
|
||||||
|
const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
|
||||||
|
const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
|
||||||
|
const uint32_t kPixelRgbBlack = RGB(0, 0, 0);
|
||||||
|
|
||||||
|
// Expands the cursor shape to add a white outline for visibility against
|
||||||
|
// dark backgrounds.
|
||||||
|
void AddCursorOutline(int width, int height, uint32_t* data) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
// If this is a transparent pixel (bgr == 0 and alpha = 0), check the
|
||||||
|
// neighbor pixels to see if this should be changed to an outline pixel.
|
||||||
|
if (*data == kPixelRgbaTransparent) {
|
||||||
|
// Change to white pixel if any neighbors (top, bottom, left, right)
|
||||||
|
// are black.
|
||||||
|
if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
|
||||||
|
(y < height - 1 && data[width] == kPixelRgbaBlack) ||
|
||||||
|
(x > 0 && data[-1] == kPixelRgbaBlack) ||
|
||||||
|
(x < width - 1 && data[1] == kPixelRgbaBlack)) {
|
||||||
|
*data = kPixelRgbaWhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Premultiplies RGB components of a pixel by its alpha component.
|
||||||
|
uint32_t AlphaMul(uint32_t pixel) {
|
||||||
|
COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel);
|
||||||
|
|
||||||
|
RGBQUAD from = *reinterpret_cast<RGBQUAD*>(&pixel);
|
||||||
|
RGBQUAD to = {
|
||||||
|
(static_cast<uint16_t>(from.rgbBlue) * from.rgbReserved) / 0xff,
|
||||||
|
(static_cast<uint16_t>(from.rgbGreen) * from.rgbReserved) / 0xff,
|
||||||
|
(static_cast<uint16_t>(from.rgbRed) * from.rgbReserved) / 0xff,
|
||||||
|
from.rgbReserved
|
||||||
|
};
|
||||||
|
|
||||||
|
return *reinterpret_cast<uint32_t*>(&to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
|
||||||
|
// |*has_alpha| is set to true if non-zero alpha is found. |stride| is expressed
|
||||||
|
// in pixels.
|
||||||
|
bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height,
|
||||||
|
bool* has_alpha) {
|
||||||
|
const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
if (plane->rgbReserved != 0) {
|
||||||
|
*has_alpha = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
plane += 1;
|
||||||
|
}
|
||||||
|
plane += stride - width;
|
||||||
|
}
|
||||||
|
|
||||||
|
*has_alpha = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MouseCursorShape* CreateMouseCursorShapeFromCursor(HDC dc, HCURSOR cursor) {
|
||||||
|
ICONINFO iinfo;
|
||||||
|
if (!GetIconInfo(cursor, &iinfo)) {
|
||||||
|
LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
|
||||||
|
<< GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hotspot_x = iinfo.xHotspot;
|
||||||
|
int hotspot_y = iinfo.yHotspot;
|
||||||
|
|
||||||
|
// Make sure the bitmaps will be freed.
|
||||||
|
win::ScopedBitmap scoped_mask(iinfo.hbmMask);
|
||||||
|
win::ScopedBitmap scoped_color(iinfo.hbmColor);
|
||||||
|
bool is_color = iinfo.hbmColor != NULL;
|
||||||
|
|
||||||
|
// Get |scoped_mask| dimensions.
|
||||||
|
BITMAP bitmap_info;
|
||||||
|
if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
|
||||||
|
LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
|
||||||
|
<< GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = bitmap_info.bmWidth;
|
||||||
|
int height = bitmap_info.bmHeight;
|
||||||
|
scoped_array<uint32_t> mask_data(new uint32_t[width * height]);
|
||||||
|
|
||||||
|
// Get pixel data from |scoped_mask| converting it to 32bpp along the way.
|
||||||
|
// GetDIBits() sets the alpha component of every pixel to 0.
|
||||||
|
BITMAPV5HEADER bmi = {0};
|
||||||
|
bmi.bV5Size = sizeof(bmi);
|
||||||
|
bmi.bV5Width = width;
|
||||||
|
bmi.bV5Height = -height; // request a top-down bitmap.
|
||||||
|
bmi.bV5Planes = 1;
|
||||||
|
bmi.bV5BitCount = kBytesPerPixel * 8;
|
||||||
|
bmi.bV5Compression = BI_RGB;
|
||||||
|
bmi.bV5AlphaMask = 0xff000000;
|
||||||
|
bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
|
||||||
|
bmi.bV5Intent = LCS_GM_BUSINESS;
|
||||||
|
if (!GetDIBits(dc,
|
||||||
|
scoped_mask,
|
||||||
|
0,
|
||||||
|
height,
|
||||||
|
mask_data.get(),
|
||||||
|
reinterpret_cast<BITMAPINFO*>(&bmi),
|
||||||
|
DIB_RGB_COLORS)) {
|
||||||
|
LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
|
||||||
|
<< GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* mask_plane = mask_data.get();
|
||||||
|
|
||||||
|
scoped_array<uint32_t> color_data;
|
||||||
|
uint32_t* color_plane = NULL;
|
||||||
|
int color_stride = 0;
|
||||||
|
bool has_alpha = false;
|
||||||
|
|
||||||
|
if (is_color) {
|
||||||
|
// Get the pixels from the color bitmap.
|
||||||
|
color_data.reset(new uint32_t[width * height]);
|
||||||
|
if (!GetDIBits(dc,
|
||||||
|
scoped_color,
|
||||||
|
0,
|
||||||
|
height,
|
||||||
|
color_data.get(),
|
||||||
|
reinterpret_cast<BITMAPINFO*>(&bmi),
|
||||||
|
DIB_RGB_COLORS)) {
|
||||||
|
LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
|
||||||
|
<< GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_plane = color_data.get();
|
||||||
|
color_stride = width;
|
||||||
|
|
||||||
|
// GetDIBits() does not provide any indication whether the bitmap has alpha
|
||||||
|
// channel, so we use HasAlphaChannel() below to find it out.
|
||||||
|
if (!HasAlphaChannel(color_plane, color_stride, width, height, &has_alpha))
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
// For non-color cursors, the mask contains both an AND and an XOR mask and
|
||||||
|
// the height includes both. Thus, the width is correct, but we need to
|
||||||
|
// divide by 2 to get the correct mask height.
|
||||||
|
height /= 2;
|
||||||
|
|
||||||
|
// The XOR mask becomes the color bitmap.
|
||||||
|
color_plane = mask_plane + (width * height);
|
||||||
|
color_stride = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct transparency from the mask if the color image does not has
|
||||||
|
// alpha channel.
|
||||||
|
if (!has_alpha) {
|
||||||
|
bool add_outline = false;
|
||||||
|
uint32_t* color = color_plane;
|
||||||
|
uint32_t* dst = color_plane;
|
||||||
|
uint32_t* mask = mask_plane;
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
// The two bitmaps combine as follows:
|
||||||
|
// mask color Windows Result Our result RGB Alpha
|
||||||
|
// 0 00 Black Black 00 ff
|
||||||
|
// 0 ff White White ff ff
|
||||||
|
// 1 00 Screen Transparent 00 00
|
||||||
|
// 1 ff Reverse-screen Black 00 ff
|
||||||
|
//
|
||||||
|
// Since we don't support XOR cursors, we replace the "Reverse Screen"
|
||||||
|
// with black. In this case, we also add an outline around the cursor
|
||||||
|
// so that it is visible against a dark background.
|
||||||
|
if (*mask == kPixelRgbWhite) {
|
||||||
|
if (*color != 0) {
|
||||||
|
add_outline = true;
|
||||||
|
*dst = kPixelRgbaBlack;
|
||||||
|
} else {
|
||||||
|
*dst = kPixelRgbaTransparent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*dst = kPixelRgbaBlack ^ *color;
|
||||||
|
}
|
||||||
|
|
||||||
|
++color;
|
||||||
|
++dst;
|
||||||
|
++mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (add_outline) {
|
||||||
|
AddCursorOutline(width, height, color_plane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_ptr<MouseCursorShape> result(new MouseCursorShape());
|
||||||
|
result->data.assign(reinterpret_cast<char*>(color_plane),
|
||||||
|
height * width * kBytesPerPixel);
|
||||||
|
result->size.set(width, height);
|
||||||
|
result->hotspot.set(hotspot_x, hotspot_y);
|
||||||
|
return result.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
26
webrtc/modules/desktop_capture/win/cursor.h
Normal file
26
webrtc/modules/desktop_capture/win/cursor.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
|
||||||
|
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
// Converts a cursor into a |MouseCursorShape| instance.
|
||||||
|
MouseCursorShape* CreateMouseCursorShapeFromCursor(
|
||||||
|
HDC dc, HCURSOR cursor);
|
||||||
|
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_H_
|
87
webrtc/modules/desktop_capture/win/cursor_unittest.cc
Normal file
87
webrtc/modules/desktop_capture/win/cursor_unittest.cc
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "testing/gmock/include/gmock/gmock.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/desktop_geometry.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/win/cursor.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/win/cursor_unittest_resources.h"
|
||||||
|
#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
|
||||||
|
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Loads |left| from resources, converts it to a |MouseCursorShape| instance
|
||||||
|
// and compares pixels with |right|. Returns true of MouseCursorShape bits
|
||||||
|
// match |right|. |right| must be a 32bpp cursor with alpha channel.
|
||||||
|
bool ConvertToMouseShapeAndCompare(unsigned left, unsigned right) {
|
||||||
|
HMODULE instance = GetModuleHandle(NULL);
|
||||||
|
|
||||||
|
// Load |left| from the EXE module's resources.
|
||||||
|
win::ScopedCursor cursor(reinterpret_cast<HCURSOR>(
|
||||||
|
LoadImage(instance, MAKEINTRESOURCE(left), IMAGE_CURSOR, 0, 0, 0)));
|
||||||
|
EXPECT_TRUE(cursor != NULL);
|
||||||
|
|
||||||
|
// Convert |cursor| to |mouse_shape|.
|
||||||
|
HDC dc = GetDC(NULL);
|
||||||
|
scoped_ptr<MouseCursorShape> mouse_shape(
|
||||||
|
CreateMouseCursorShapeFromCursor(dc, cursor));
|
||||||
|
ReleaseDC(NULL, dc);
|
||||||
|
|
||||||
|
EXPECT_TRUE(mouse_shape.get());
|
||||||
|
|
||||||
|
// Load |right|.
|
||||||
|
cursor.Set(reinterpret_cast<HCURSOR>(
|
||||||
|
LoadImage(instance, MAKEINTRESOURCE(right), IMAGE_CURSOR, 0, 0, 0)));
|
||||||
|
|
||||||
|
ICONINFO iinfo;
|
||||||
|
EXPECT_TRUE(GetIconInfo(cursor, &iinfo));
|
||||||
|
EXPECT_TRUE(iinfo.hbmColor);
|
||||||
|
|
||||||
|
// Make sure the bitmaps will be freed.
|
||||||
|
win::ScopedBitmap scoped_mask(iinfo.hbmMask);
|
||||||
|
win::ScopedBitmap scoped_color(iinfo.hbmColor);
|
||||||
|
|
||||||
|
// Get |scoped_color| dimensions.
|
||||||
|
BITMAP bitmap_info;
|
||||||
|
EXPECT_TRUE(GetObject(scoped_color, sizeof(bitmap_info), &bitmap_info));
|
||||||
|
|
||||||
|
int width = bitmap_info.bmWidth;
|
||||||
|
int height = bitmap_info.bmHeight;
|
||||||
|
EXPECT_TRUE(DesktopSize(width, height).equals(mouse_shape->size));
|
||||||
|
|
||||||
|
// Get the pixels from |scoped_color|.
|
||||||
|
int size = width * height;
|
||||||
|
scoped_array<uint32_t> data(new uint32_t[size]);
|
||||||
|
EXPECT_TRUE(GetBitmapBits(scoped_color, size * sizeof(uint32_t), data.get()));
|
||||||
|
|
||||||
|
// Compare the 32bpp image in |mouse_shape| with the one loaded from |right|.
|
||||||
|
return memcmp(data.get(), mouse_shape->data.data(),
|
||||||
|
size * sizeof(uint32_t)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(MouseCursorShapeTest, MatchCursors) {
|
||||||
|
EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR1_24BPP,
|
||||||
|
IDD_CURSOR1_32BPP));
|
||||||
|
|
||||||
|
EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR1_8BPP,
|
||||||
|
IDD_CURSOR1_32BPP));
|
||||||
|
|
||||||
|
EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR2_1BPP,
|
||||||
|
IDD_CURSOR2_32BPP));
|
||||||
|
|
||||||
|
EXPECT_TRUE(ConvertToMouseShapeAndCompare(IDD_CURSOR3_4BPP,
|
||||||
|
IDD_CURSOR3_32BPP));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webrtc
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
|
||||||
|
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
|
||||||
|
|
||||||
|
#define IDD_CURSOR1_24BPP 101
|
||||||
|
#define IDD_CURSOR1_32BPP 102
|
||||||
|
#define IDD_CURSOR1_8BPP 103
|
||||||
|
|
||||||
|
#define IDD_CURSOR2_1BPP 104
|
||||||
|
#define IDD_CURSOR2_32BPP 105
|
||||||
|
|
||||||
|
#define IDD_CURSOR3_4BPP 106
|
||||||
|
#define IDD_CURSOR3_32BPP 107
|
||||||
|
|
||||||
|
#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_CURSOR_UNITTEST_RESOURCES_H_
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "webrtc/modules/desktop_capture/win/cursor_unittest_resources.h"
|
||||||
|
|
||||||
|
// These cursors are matched with their less than 32bpp counterparts below.
|
||||||
|
IDD_CURSOR1_32BPP CURSOR "cursor_test_data/1_32bpp.cur"
|
||||||
|
IDD_CURSOR2_32BPP CURSOR "cursor_test_data/2_32bpp.cur"
|
||||||
|
IDD_CURSOR3_32BPP CURSOR "cursor_test_data/3_32bpp.cur"
|
||||||
|
|
||||||
|
// Matches IDD_CURSOR1_32BPP.
|
||||||
|
IDD_CURSOR1_24BPP CURSOR "cursor_test_data/1_24bpp.cur"
|
||||||
|
|
||||||
|
// Matches IDD_CURSOR1_32BPP.
|
||||||
|
IDD_CURSOR1_8BPP CURSOR "cursor_test_data/1_8bpp.cur"
|
||||||
|
|
||||||
|
// Matches IDD_CURSOR2_32BPP.
|
||||||
|
IDD_CURSOR2_1BPP CURSOR "cursor_test_data/2_1bpp.cur"
|
||||||
|
|
||||||
|
// Matches IDD_CURSOR3_32BPP.
|
||||||
|
IDD_CURSOR3_4BPP CURSOR "cursor_test_data/3_4bpp.cur"
|
95
webrtc/modules/desktop_capture/win/scoped_gdi_object.h
Normal file
95
webrtc/modules/desktop_capture/win/scoped_gdi_object.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
|
||||||
|
#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "webrtc/system_wrappers/interface/constructor_magic.h"
|
||||||
|
#include "webrtc/typedefs.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace win {
|
||||||
|
|
||||||
|
// Scoper for GDI objects.
|
||||||
|
template<class T, class Traits>
|
||||||
|
class ScopedGDIObject {
|
||||||
|
public:
|
||||||
|
ScopedGDIObject() : handle_(NULL) {}
|
||||||
|
explicit ScopedGDIObject(T object) : handle_(object) {}
|
||||||
|
|
||||||
|
~ScopedGDIObject() {
|
||||||
|
Traits::Close(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
T Get() {
|
||||||
|
return handle_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set(T object) {
|
||||||
|
if (handle_ && object != handle_)
|
||||||
|
Traits::Close(handle_);
|
||||||
|
handle_ = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGDIObject& operator=(T object) {
|
||||||
|
Set(object);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
T release() {
|
||||||
|
T object = handle_;
|
||||||
|
handle_ = NULL;
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T() { return handle_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T handle_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ScopedGDIObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The traits class that uses DeleteObject() to close a handle.
|
||||||
|
template <typename T>
|
||||||
|
class DeleteObjectTraits {
|
||||||
|
public:
|
||||||
|
// Closes the handle.
|
||||||
|
static void Close(T handle) {
|
||||||
|
if (handle)
|
||||||
|
DeleteObject(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(DeleteObjectTraits);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The traits class that uses DestroyCursor() to close a handle.
|
||||||
|
class DestroyCursorTraits {
|
||||||
|
public:
|
||||||
|
// Closes the handle.
|
||||||
|
static void Close(HCURSOR handle) {
|
||||||
|
if (handle)
|
||||||
|
DestroyCursor(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(DestroyCursorTraits);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef ScopedGDIObject<HBITMAP, DeleteObjectTraits<HBITMAP> > ScopedBitmap;
|
||||||
|
typedef ScopedGDIObject<HCURSOR, DestroyCursorTraits> ScopedCursor;
|
||||||
|
|
||||||
|
} // namespace win
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCOPED_GDI_HANDLE_H_
|
Loading…
x
Reference in New Issue
Block a user