blob: e3c272ce82ca4fd736ae342757150a8a96cf92a8 [file] [log] [blame]
alexeypa@chromium.org4af08782013-06-10 22:29:17 +00001/*
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/modules/desktop_capture/win/cursor.h"
12
13#include <algorithm>
14
15#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
16#include "webrtc/modules/desktop_capture/desktop_frame.h"
17#include "webrtc/modules/desktop_capture/desktop_geometry.h"
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +000018#include "webrtc/modules/desktop_capture/mouse_cursor.h"
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000019#include "webrtc/system_wrappers/interface/compile_assert.h"
20#include "webrtc/system_wrappers/interface/logging.h"
21#include "webrtc/system_wrappers/interface/scoped_ptr.h"
22#include "webrtc/typedefs.h"
23
24namespace webrtc {
25
26namespace {
27
28#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
29
30#define RGBA(r, g, b, a) \
31 ((((a) << 24) & 0xff000000) | \
32 (((b) << 16) & 0xff0000) | \
33 (((g) << 8) & 0xff00) | \
34 ((r) & 0xff))
35
36#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
37
38#define RGBA(r, g, b, a) \
39 ((((r) << 24) & 0xff000000) | \
40 (((g) << 16) & 0xff0000) | \
41 (((b) << 8) & 0xff00) | \
42 ((a) & 0xff))
43
44#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
45
46const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
47
48// Pixel colors used when generating cursor outlines.
49const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
50const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
51const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
52
53const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
54const uint32_t kPixelRgbBlack = RGB(0, 0, 0);
55
56// Expands the cursor shape to add a white outline for visibility against
57// dark backgrounds.
58void AddCursorOutline(int width, int height, uint32_t* data) {
59 for (int y = 0; y < height; y++) {
60 for (int x = 0; x < width; x++) {
61 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
62 // neighbor pixels to see if this should be changed to an outline pixel.
63 if (*data == kPixelRgbaTransparent) {
64 // Change to white pixel if any neighbors (top, bottom, left, right)
65 // are black.
66 if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
67 (y < height - 1 && data[width] == kPixelRgbaBlack) ||
68 (x > 0 && data[-1] == kPixelRgbaBlack) ||
69 (x < width - 1 && data[1] == kPixelRgbaBlack)) {
70 *data = kPixelRgbaWhite;
71 }
72 }
73 data++;
74 }
75 }
76}
77
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000078// Premultiplies RGB components of the pixel data in the given image by
79// the corresponding alpha components.
80void AlphaMul(uint32_t* data, int width, int height) {
andrew@webrtc.org31628aa2013-10-22 12:50:00 +000081 COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel,
82 size_of_uint32_should_be_the_bytes_per_pixel);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000083
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000084 for (uint32_t* data_end = data + width * height; data != data_end; ++data) {
85 RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data);
86 RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data);
87 to->rgbBlue =
88 (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff;
89 to->rgbGreen =
90 (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff;
91 to->rgbRed =
92 (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff;
93 }
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000094}
95
96// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +000097// Returns true if non-zero alpha is found. |stride| is expressed in pixels.
98bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000099 const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
100 for (int y = 0; y < height; ++y) {
101 for (int x = 0; x < width; ++x) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000102 if (plane->rgbReserved != 0)
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000103 return true;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000104 plane += 1;
105 }
106 plane += stride - width;
107 }
108
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000109 return false;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000110}
111
112} // namespace
113
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000114MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000115 ICONINFO iinfo;
116 if (!GetIconInfo(cursor, &iinfo)) {
117 LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
118 << GetLastError();
119 return NULL;
120 }
121
122 int hotspot_x = iinfo.xHotspot;
123 int hotspot_y = iinfo.yHotspot;
124
125 // Make sure the bitmaps will be freed.
126 win::ScopedBitmap scoped_mask(iinfo.hbmMask);
127 win::ScopedBitmap scoped_color(iinfo.hbmColor);
128 bool is_color = iinfo.hbmColor != NULL;
129
130 // Get |scoped_mask| dimensions.
131 BITMAP bitmap_info;
132 if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
133 LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
134 << GetLastError();
135 return NULL;
136 }
137
138 int width = bitmap_info.bmWidth;
139 int height = bitmap_info.bmHeight;
andrew@webrtc.org8f693302014-04-25 23:10:28 +0000140 scoped_ptr<uint32_t[]> mask_data(new uint32_t[width * height]);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000141
142 // Get pixel data from |scoped_mask| converting it to 32bpp along the way.
143 // GetDIBits() sets the alpha component of every pixel to 0.
144 BITMAPV5HEADER bmi = {0};
145 bmi.bV5Size = sizeof(bmi);
146 bmi.bV5Width = width;
147 bmi.bV5Height = -height; // request a top-down bitmap.
148 bmi.bV5Planes = 1;
149 bmi.bV5BitCount = kBytesPerPixel * 8;
150 bmi.bV5Compression = BI_RGB;
151 bmi.bV5AlphaMask = 0xff000000;
152 bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
153 bmi.bV5Intent = LCS_GM_BUSINESS;
154 if (!GetDIBits(dc,
155 scoped_mask,
156 0,
157 height,
158 mask_data.get(),
159 reinterpret_cast<BITMAPINFO*>(&bmi),
160 DIB_RGB_COLORS)) {
161 LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
162 << GetLastError();
163 return NULL;
164 }
165
166 uint32_t* mask_plane = mask_data.get();
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000167 scoped_ptr<DesktopFrame> image(
168 new BasicDesktopFrame(DesktopSize(width, height)));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000169 bool has_alpha = false;
170
171 if (is_color) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000172 image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000173 // Get the pixels from the color bitmap.
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000174 if (!GetDIBits(dc,
175 scoped_color,
176 0,
177 height,
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000178 image->data(),
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000179 reinterpret_cast<BITMAPINFO*>(&bmi),
180 DIB_RGB_COLORS)) {
181 LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
182 << GetLastError();
183 return NULL;
184 }
185
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000186 // GetDIBits() does not provide any indication whether the bitmap has alpha
187 // channel, so we use HasAlphaChannel() below to find it out.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000188 has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()),
189 width, width, height);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000190 } else {
191 // For non-color cursors, the mask contains both an AND and an XOR mask and
192 // the height includes both. Thus, the width is correct, but we need to
193 // divide by 2 to get the correct mask height.
194 height /= 2;
195
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000196 image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
197
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000198 // The XOR mask becomes the color bitmap.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000199 memcpy(
jiayl@webrtc.orgbac5f0f2014-07-15 20:32:03 +0000200 image->data(), mask_plane + (width * height), image->stride() * height);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000201 }
202
203 // Reconstruct transparency from the mask if the color image does not has
204 // alpha channel.
205 if (!has_alpha) {
206 bool add_outline = false;
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000207 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000208 uint32_t* mask = mask_plane;
209 for (int y = 0; y < height; y++) {
210 for (int x = 0; x < width; x++) {
211 // The two bitmaps combine as follows:
212 // mask color Windows Result Our result RGB Alpha
213 // 0 00 Black Black 00 ff
214 // 0 ff White White ff ff
215 // 1 00 Screen Transparent 00 00
216 // 1 ff Reverse-screen Black 00 ff
217 //
218 // Since we don't support XOR cursors, we replace the "Reverse Screen"
219 // with black. In this case, we also add an outline around the cursor
220 // so that it is visible against a dark background.
221 if (*mask == kPixelRgbWhite) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000222 if (*dst != 0) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000223 add_outline = true;
224 *dst = kPixelRgbaBlack;
225 } else {
226 *dst = kPixelRgbaTransparent;
227 }
228 } else {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000229 *dst = kPixelRgbaBlack ^ *dst;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000230 }
231
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000232 ++dst;
233 ++mask;
234 }
235 }
236 if (add_outline) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000237 AddCursorOutline(
238 width, height, reinterpret_cast<uint32_t*>(image->data()));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000239 }
240 }
241
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000242 // Pre-multiply the resulting pixels since MouseCursor uses premultiplied
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +0000243 // images.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000244 AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height);
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +0000245
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000246 return new MouseCursor(
247 image.release(), DesktopVector(hotspot_x, hotspot_y));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000248}
249
250} // namespace webrtc