blob: 76eed7742356324ed99f667a61189baf998bd5c0 [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"
18#include "webrtc/system_wrappers/interface/compile_assert.h"
19#include "webrtc/system_wrappers/interface/logging.h"
20#include "webrtc/system_wrappers/interface/scoped_ptr.h"
21#include "webrtc/typedefs.h"
22
23namespace webrtc {
24
25namespace {
26
27#if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
28
29#define RGBA(r, g, b, a) \
30 ((((a) << 24) & 0xff000000) | \
31 (((b) << 16) & 0xff0000) | \
32 (((g) << 8) & 0xff00) | \
33 ((r) & 0xff))
34
35#else // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
36
37#define RGBA(r, g, b, a) \
38 ((((r) << 24) & 0xff000000) | \
39 (((g) << 16) & 0xff0000) | \
40 (((b) << 8) & 0xff00) | \
41 ((a) & 0xff))
42
43#endif // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
44
45const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
46
47// Pixel colors used when generating cursor outlines.
48const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
49const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
50const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
51
52const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
53const uint32_t kPixelRgbBlack = RGB(0, 0, 0);
54
55// Expands the cursor shape to add a white outline for visibility against
56// dark backgrounds.
57void AddCursorOutline(int width, int height, uint32_t* data) {
58 for (int y = 0; y < height; y++) {
59 for (int x = 0; x < width; x++) {
60 // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
61 // neighbor pixels to see if this should be changed to an outline pixel.
62 if (*data == kPixelRgbaTransparent) {
63 // Change to white pixel if any neighbors (top, bottom, left, right)
64 // are black.
65 if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
66 (y < height - 1 && data[width] == kPixelRgbaBlack) ||
67 (x > 0 && data[-1] == kPixelRgbaBlack) ||
68 (x < width - 1 && data[1] == kPixelRgbaBlack)) {
69 *data = kPixelRgbaWhite;
70 }
71 }
72 data++;
73 }
74 }
75}
76
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000077// Premultiplies RGB components of the pixel data in the given image by
78// the corresponding alpha components.
79void AlphaMul(uint32_t* data, int width, int height) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000080 COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel);
81
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000082 for (uint32_t* data_end = data + width * height; data != data_end; ++data) {
83 RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data);
84 RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data);
85 to->rgbBlue =
86 (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff;
87 to->rgbGreen =
88 (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff;
89 to->rgbRed =
90 (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff;
91 }
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000092}
93
94// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
95// |*has_alpha| is set to true if non-zero alpha is found. |stride| is expressed
96// in pixels.
97bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height,
98 bool* has_alpha) {
99 const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
100 for (int y = 0; y < height; ++y) {
101 for (int x = 0; x < width; ++x) {
102 if (plane->rgbReserved != 0) {
103 *has_alpha = true;
104 return true;
105 }
106 plane += 1;
107 }
108 plane += stride - width;
109 }
110
111 *has_alpha = false;
112 return true;
113}
114
115} // namespace
116
117MouseCursorShape* CreateMouseCursorShapeFromCursor(HDC dc, HCURSOR cursor) {
118 ICONINFO iinfo;
119 if (!GetIconInfo(cursor, &iinfo)) {
120 LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
121 << GetLastError();
122 return NULL;
123 }
124
125 int hotspot_x = iinfo.xHotspot;
126 int hotspot_y = iinfo.yHotspot;
127
128 // Make sure the bitmaps will be freed.
129 win::ScopedBitmap scoped_mask(iinfo.hbmMask);
130 win::ScopedBitmap scoped_color(iinfo.hbmColor);
131 bool is_color = iinfo.hbmColor != NULL;
132
133 // Get |scoped_mask| dimensions.
134 BITMAP bitmap_info;
135 if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
136 LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
137 << GetLastError();
138 return NULL;
139 }
140
141 int width = bitmap_info.bmWidth;
142 int height = bitmap_info.bmHeight;
143 scoped_array<uint32_t> mask_data(new uint32_t[width * height]);
144
145 // Get pixel data from |scoped_mask| converting it to 32bpp along the way.
146 // GetDIBits() sets the alpha component of every pixel to 0.
147 BITMAPV5HEADER bmi = {0};
148 bmi.bV5Size = sizeof(bmi);
149 bmi.bV5Width = width;
150 bmi.bV5Height = -height; // request a top-down bitmap.
151 bmi.bV5Planes = 1;
152 bmi.bV5BitCount = kBytesPerPixel * 8;
153 bmi.bV5Compression = BI_RGB;
154 bmi.bV5AlphaMask = 0xff000000;
155 bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
156 bmi.bV5Intent = LCS_GM_BUSINESS;
157 if (!GetDIBits(dc,
158 scoped_mask,
159 0,
160 height,
161 mask_data.get(),
162 reinterpret_cast<BITMAPINFO*>(&bmi),
163 DIB_RGB_COLORS)) {
164 LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
165 << GetLastError();
166 return NULL;
167 }
168
169 uint32_t* mask_plane = mask_data.get();
170
171 scoped_array<uint32_t> color_data;
172 uint32_t* color_plane = NULL;
173 int color_stride = 0;
174 bool has_alpha = false;
175
176 if (is_color) {
177 // Get the pixels from the color bitmap.
178 color_data.reset(new uint32_t[width * height]);
179 if (!GetDIBits(dc,
180 scoped_color,
181 0,
182 height,
183 color_data.get(),
184 reinterpret_cast<BITMAPINFO*>(&bmi),
185 DIB_RGB_COLORS)) {
186 LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
187 << GetLastError();
188 return NULL;
189 }
190
191 color_plane = color_data.get();
192 color_stride = width;
193
194 // GetDIBits() does not provide any indication whether the bitmap has alpha
195 // channel, so we use HasAlphaChannel() below to find it out.
196 if (!HasAlphaChannel(color_plane, color_stride, width, height, &has_alpha))
197 return NULL;
198 } else {
199 // For non-color cursors, the mask contains both an AND and an XOR mask and
200 // the height includes both. Thus, the width is correct, but we need to
201 // divide by 2 to get the correct mask height.
202 height /= 2;
203
204 // The XOR mask becomes the color bitmap.
205 color_plane = mask_plane + (width * height);
206 color_stride = width;
207 }
208
209 // Reconstruct transparency from the mask if the color image does not has
210 // alpha channel.
211 if (!has_alpha) {
212 bool add_outline = false;
213 uint32_t* color = color_plane;
214 uint32_t* dst = color_plane;
215 uint32_t* mask = mask_plane;
216 for (int y = 0; y < height; y++) {
217 for (int x = 0; x < width; x++) {
218 // The two bitmaps combine as follows:
219 // mask color Windows Result Our result RGB Alpha
220 // 0 00 Black Black 00 ff
221 // 0 ff White White ff ff
222 // 1 00 Screen Transparent 00 00
223 // 1 ff Reverse-screen Black 00 ff
224 //
225 // Since we don't support XOR cursors, we replace the "Reverse Screen"
226 // with black. In this case, we also add an outline around the cursor
227 // so that it is visible against a dark background.
228 if (*mask == kPixelRgbWhite) {
229 if (*color != 0) {
230 add_outline = true;
231 *dst = kPixelRgbaBlack;
232 } else {
233 *dst = kPixelRgbaTransparent;
234 }
235 } else {
236 *dst = kPixelRgbaBlack ^ *color;
237 }
238
239 ++color;
240 ++dst;
241 ++mask;
242 }
243 }
244 if (add_outline) {
245 AddCursorOutline(width, height, color_plane);
246 }
247 }
248
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +0000249 // Pre-multiply the resulting pixels since MouseCursorShape uses premultiplied
250 // images.
251 AlphaMul(color_plane, width, height);
252
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000253 scoped_ptr<MouseCursorShape> result(new MouseCursorShape());
254 result->data.assign(reinterpret_cast<char*>(color_plane),
255 height * width * kBytesPerPixel);
256 result->size.set(width, height);
257 result->hotspot.set(hotspot_x, hotspot_y);
258 return result.release();
259}
260
261} // namespace webrtc